Last active
July 13, 2016 01:27
-
-
Save samgro/b1d7f8e27e8d9122fab0 to your computer and use it in GitHub Desktop.
Revisions
-
samgro revised this gist
Nov 11, 2015 . 1 changed file with 13 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,13 @@ Copyright (c) 2015, Foursquare Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -
samgro revised this gist
Nov 11, 2015 . 1 changed file with 1 addition and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,9 +1,7 @@ // // FSQSplitViewController.h // // Copyright (c) 2015 Foursquare. All rights reserved. // #import "FSCoreViewController.h" -
samgro created this gist
Nov 11, 2015 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,73 @@ // // FSQSplitViewController.h // foursquare-ios-core // // Created by Vineet Shah on 10/6/15. // // #import "FSCoreViewController.h" NS_ASSUME_NONNULL_BEGIN; @class FSQSplitViewController; typedef NS_ENUM(NSUInteger, FSQSplitViewDisplayMode) { FSQSplitViewDisplayModePrimary = 0, FSQSplitViewDisplayModeSecondary, }; @protocol FSQSplitViewChildControllerDelegate <NSObject> @optional - (void) splitViewController:(FSQSplitViewController *)splitViewController willShowSecondaryViewController:(UIViewController<FSQSplitViewChildControllerDelegate> *)secondaryViewController; - (void) splitViewController:(FSQSplitViewController *)splitViewController didShowSecondaryViewController:(UIViewController<FSQSplitViewChildControllerDelegate> *)secondaryViewController; - (void) splitViewController:(FSQSplitViewController *)splitViewController willHideSecondaryViewController:(UIViewController<FSQSplitViewChildControllerDelegate> *)secondaryViewController; - (void) splitViewController:(FSQSplitViewController *)splitViewController didHideSecondaryViewController:(UIViewController<FSQSplitViewChildControllerDelegate> *)secondaryViewController; // These delegate methods only get called when views are side-by-side so that the views don't have side effects // on each other while off screen. - (void)siblingViewController:(UIViewController *)siblingViewController didSelectItemAtIndex:(NSInteger)index userInfo:(nullable NSDictionary *)userInfo; - (void)siblingViewController:(UIViewController *)siblingViewController didDeselectItemAtIndex:(NSInteger)index userInfo:(nullable NSDictionary *)userInfo; @end @interface FSQSplitViewController : FSCoreViewController @property (nonatomic, readonly) UIViewController<FSQSplitViewChildControllerDelegate> *primaryViewController; @property (nonatomic, readonly) UIViewController<FSQSplitViewChildControllerDelegate> *secondaryViewController; @property (nonatomic, readonly) FSQSplitViewDisplayMode displayMode; @property (nonatomic) BOOL dismissSecondaryViewControllerOnBackButtonPress; // default = YES - (instancetype)initWithPrimaryViewController:(UIViewController<FSQSplitViewChildControllerDelegate> *)primaryViewController secondaryViewController:(UIViewController<FSQSplitViewChildControllerDelegate> *)secondaryViewController; - (void)childViewController:(UIViewController *)childViewController didSelectItemAtIndex:(NSInteger)index userInfo:(nullable NSDictionary *)userInfo; - (void)childViewController:(UIViewController *)childViewController didDeselectItemAtIndex:(NSInteger)index userInfo:(nullable NSDictionary *)userInfo; - (void)setDisplayMode:(FSQSplitViewDisplayMode)displayMode animated:(BOOL)animated; - (void)showSecondaryViewControllerAnimated:(BOOL)animated withAdditionalAnimations:(nullable void (^)())animations completionBlock:(nullable void (^)())completion; - (void)hideSecondaryViewControllerAnimated:(BOOL)animated withAdditionalAnimations:(nullable void (^)())animations completionBlock:(nullable void (^)())completion; @end NS_ASSUME_NONNULL_END; This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,244 @@ // // FSQSplitViewController.m // // Copyright (c) 2015 Foursquare. All rights reserved. // #import "FSQSplitViewController.h" static const CGFloat kPrimaryWidthPercentage = 0.4; static const CGFloat kSplitViewAnimationDuration = 0.4; static const CGFloat kSplitViewAnimationDampingRatio = 1.0; @interface FSQSplitViewController () @property (nonatomic) UIView *verticalSeparator; @property (nonatomic) BOOL isAnimating; @end @implementation FSQSplitViewController - (instancetype)initWithPrimaryViewController:(UIViewController<FSQSplitViewChildControllerDelegate> *)primaryViewController secondaryViewController:(UIViewController<FSQSplitViewChildControllerDelegate> *)secondaryViewController { self = [super init]; if (self) { _primaryViewController = primaryViewController; _secondaryViewController = secondaryViewController; _dismissSecondaryViewControllerOnBackButtonPress = YES; } return self; } - (void)viewDidLoad { [super viewDidLoad]; [self addChildViewController:self.primaryViewController]; [self addChildViewController:self.secondaryViewController]; [self.view addSubview:self.primaryViewController.view]; [self.view addSubview:self.secondaryViewController.view]; [self.primaryViewController didMoveToParentViewController:self]; [self.secondaryViewController didMoveToParentViewController:self]; self.verticalSeparator = [UIView lineVerticalWithTop:0.0 bottom:self.view.height left:0.0 color:[UIColor fsCellSeparatorColor]]; [self.view addSubview:self.verticalSeparator]; } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; [self setupViewsWithSize:self.view.size traitCollection:self.traitCollection]; } - (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator { [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator]; _displayMode = FSQSplitViewDisplayModePrimary; } - (void)setDisplayMode:(FSQSplitViewDisplayMode)displayMode animated:(BOOL)animated { if (_displayMode != displayMode) { _displayMode = displayMode; switch (displayMode) { case FSQSplitViewDisplayModePrimary: [self hideSecondaryViewControllerAnimated:animated withAdditionalAnimations:nil completionBlock:nil]; break; case FSQSplitViewDisplayModeSecondary: [self showSecondaryViewControllerAnimated:NO withAdditionalAnimations:nil completionBlock:nil]; break; } } } - (CGSize)sizeForChildContentContainer:(id<UIContentContainer>)container withParentContainerSize:(CGSize)parentSize { if (container == self.primaryViewController) { return [self frameForPrimaryViewControllerWithParentSize:parentSize traitCollection:self.traitCollection].size; } else if (container == self.secondaryViewController) { return [self frameForSecondaryViewControllerWithParentSize:parentSize traitCollection:self.traitCollection].size; } else { NSAssert(NO, @"Invalid child view controller: %@", container); return CGSizeZero; } } - (void)setupViewsWithSize:(CGSize)size traitCollection:(UITraitCollection *)traitCollection { if (!self.isAnimating) { self.primaryViewController.view.frame = [self frameForPrimaryViewControllerWithParentSize:size traitCollection:traitCollection]; self.secondaryViewController.view.frame = [self frameForSecondaryViewControllerWithParentSize:size traitCollection:traitCollection]; self.secondaryViewController.view.hidden = (traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact && self.displayMode == FSQSplitViewDisplayModePrimary); self.verticalSeparator.hidden = (traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact); self.verticalSeparator.left = self.primaryViewController.view.right; } } - (void)childViewController:(UIViewController *)childViewController didSelectItemAtIndex:(NSInteger)index userInfo:(NSDictionary *)userInfo { NSParameterAssert(childViewController == self.primaryViewController || childViewController == self.secondaryViewController); if (self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) { return; } if (self.primaryViewController != childViewController && [self.primaryViewController respondsToSelector:@selector(siblingViewController:didSelectItemAtIndex:userInfo:)]) { [self.primaryViewController siblingViewController:childViewController didSelectItemAtIndex:index userInfo:userInfo]; } if (self.secondaryViewController != childViewController && [self.secondaryViewController respondsToSelector:@selector(siblingViewController:didSelectItemAtIndex:userInfo:)]) { [self.secondaryViewController siblingViewController:childViewController didSelectItemAtIndex:index userInfo:userInfo]; } } - (void)childViewController:(UIViewController *)childViewController didDeselectItemAtIndex:(NSInteger)index userInfo:(NSDictionary *)userInfo { NSParameterAssert(childViewController == self.primaryViewController || childViewController == self.secondaryViewController); if (self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) { return; } if (self.primaryViewController != childViewController && [self.primaryViewController respondsToSelector:@selector(siblingViewController:didSelectItemAtIndex:userInfo:)]) { [self.primaryViewController siblingViewController:childViewController didDeselectItemAtIndex:index userInfo:userInfo]; } if (self.secondaryViewController != childViewController && [self.secondaryViewController respondsToSelector:@selector(siblingViewController:didSelectItemAtIndex:userInfo:)]) { [self.secondaryViewController siblingViewController:childViewController didDeselectItemAtIndex:index userInfo:userInfo]; } } - (void)showSecondaryViewControllerAnimated:(BOOL)animated withAdditionalAnimations:(void (^)())animations completionBlock:(void (^)())completion { if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) { for (UIViewController<FSQSplitViewChildControllerDelegate> *childViewController in @[self.primaryViewController, self.secondaryViewController]) { if ([childViewController respondsToSelector:@selector(splitViewController:willShowSecondaryViewController:)]) { [childViewController splitViewController:self willShowSecondaryViewController:self.secondaryViewController]; } } _displayMode = FSQSplitViewDisplayModeSecondary; self.isAnimating = YES; self.secondaryViewController.view.frame = self.view.bounds; self.secondaryViewController.view.bottom = 0.0; self.secondaryViewController.view.hidden = NO; [UIView animateWithDuration:animated ? kSplitViewAnimationDuration : 0.0 delay:0.0 usingSpringWithDamping:kSplitViewAnimationDampingRatio initialSpringVelocity:0.0 options:0 animations:^{ self.secondaryViewController.view.top = 0.f; if (animations) { animations(); } } completion:^(BOOL finished) { self.isAnimating = NO; if (completion) { completion(); } }]; for (UIViewController<FSQSplitViewChildControllerDelegate> *childViewController in @[self.primaryViewController, self.secondaryViewController]) { if ([childViewController respondsToSelector:@selector(splitViewController:didShowSecondaryViewController:)]) { [childViewController splitViewController:self didShowSecondaryViewController:self.secondaryViewController]; } } } } - (void)hideSecondaryViewControllerAnimated:(BOOL)animated withAdditionalAnimations:(void (^)())animations completionBlock:(void (^)())completion { if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) { for (UIViewController<FSQSplitViewChildControllerDelegate> *childViewController in @[self.primaryViewController, self.secondaryViewController]) { if ([childViewController respondsToSelector:@selector(splitViewController:willHideSecondaryViewController:)]) { [childViewController splitViewController:self willHideSecondaryViewController:self.secondaryViewController]; } } _displayMode = FSQSplitViewDisplayModePrimary; self.isAnimating = YES; [UIView animateWithDuration:animated ? kSplitViewAnimationDuration : 0.0 delay:0.0 usingSpringWithDamping:kSplitViewAnimationDampingRatio initialSpringVelocity:0.0 options:0 animations:^{ self.secondaryViewController.view.bottom = 0.f; if (animations) { animations(); } } completion:^(BOOL finished) { self.isAnimating = NO; self.secondaryViewController.view.hidden = YES; if (completion) { completion(); } }]; for (UIViewController<FSQSplitViewChildControllerDelegate> *childViewController in @[self.primaryViewController, self.secondaryViewController]) { if ([childViewController respondsToSelector:@selector(splitViewController:didHideSecondaryViewController:)]) { [childViewController splitViewController:self didHideSecondaryViewController:self.secondaryViewController]; } } } } - (void)backButtonSelected:(id)sender { if (self.displayMode == FSQSplitViewDisplayModeSecondary && self.dismissSecondaryViewControllerOnBackButtonPress) { [self hideSecondaryViewControllerAnimated:YES withAdditionalAnimations:nil completionBlock:nil]; } else { [super backButtonSelected:sender]; } } #pragma mark - Helpers - (CGRect)frameForPrimaryViewControllerWithParentSize:(CGSize)size traitCollection:(UITraitCollection *)traitCollection { if (traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) { return CGRectMake(0.0, 0.0, size.width * kPrimaryWidthPercentage, size.height); } else { return CGRectMake(0.0, 0.0, size.width, size.height); } } - (CGRect)frameForSecondaryViewControllerWithParentSize:(CGSize)size traitCollection:(UITraitCollection *)traitCollection { if (traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) { return CGRectMake(size.width * kPrimaryWidthPercentage, 0.0, size.width * (1 - kPrimaryWidthPercentage), size.height); } else { return CGRectMake(0.0, 0.0, size.width, size.height); } } @end