element-ios/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m

553 lines
23 KiB
Mathematica
Raw Normal View History

/*
Copyright 2015 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
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.
*/
#import "SegmentedViewController.h"
#import "ThemeService.h"
#ifdef IS_SHARE_EXTENSION
#import "RiotShareExtension-Swift.h"
#else
#import "Riot-Swift.h"
#endif
@interface SegmentedViewController ()
{
// Tell whether the segmented view is appeared (see viewWillAppear/viewWillDisappear).
BOOL isViewAppeared;
// list of displayed UIViewControllers
NSArray* viewControllers;
// The constraints of the displayed viewController
NSLayoutConstraint *displayedVCTopConstraint;
NSLayoutConstraint *displayedVCLeftConstraint;
NSLayoutConstraint *displayedVCWidthConstraint;
NSLayoutConstraint *displayedVCHeightConstraint;
// list of NSString
NSArray* sectionTitles;
// list of section labels
NSArray* sectionLabels;
// the selected marker view
UIView* selectedMarkerView;
NSLayoutConstraint *leftMarkerViewConstraint;
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
id kThemeServiceDidChangeThemeNotificationObserver;
}
@end
@implementation SegmentedViewController
#pragma mark - Class methods
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([SegmentedViewController class])
bundle:[NSBundle bundleForClass:[SegmentedViewController class]]];
}
+ (instancetype)segmentedViewController
{
return [[[self class] alloc] initWithNibName:NSStringFromClass([SegmentedViewController class])
bundle:[NSBundle bundleForClass:[SegmentedViewController class]]];
}
/**
init the segmentedViewController with a list of UIViewControllers.
@param titles the section tiles
@param viewControllers the list of viewControllers to display.
@param defaultSelected index of the default selected UIViewController in the list.
*/
- (void)initWithTitles:(NSArray*)titles viewControllers:(NSArray*)someViewControllers defaultSelected:(NSUInteger)defaultSelected
{
viewControllers = someViewControllers;
sectionTitles = titles;
_selectedIndex = defaultSelected;
}
- (void)destroy
{
for (id viewController in viewControllers)
{
if ([viewController respondsToSelector:@selector(destroy)])
{
[viewController destroy];
}
}
viewControllers = nil;
sectionTitles = nil;
sectionLabels = nil;
if (selectedMarkerView)
{
[selectedMarkerView removeFromSuperview];
selectedMarkerView = nil;
}
if (kThemeServiceDidChangeThemeNotificationObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver];
kThemeServiceDidChangeThemeNotificationObserver = nil;
}
[super destroy];
}
- (void)setSelectedIndex:(NSUInteger)selectedIndex
{
if (_selectedIndex != selectedIndex)
{
_selectedIndex = selectedIndex;
[self displaySelectedViewController];
}
}
- (NSArray<UIViewController *> *)viewControllers
{
return viewControllers;
}
#pragma mark -
- (void)finalizeInit
{
[super finalizeInit];
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.sectionHeaderTintColor = ThemeService.theme.tintColor;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Check whether the view controller has been pushed via storyboard
if (!self.viewControllerContainer)
{
// Instantiate view controller objects
[[[self class] nib] instantiateWithOwner:self options:nil];
}
// Adjust Top
[NSLayoutConstraint deactivateConstraints:@[self.selectionContainerTopConstraint]];
// it is not possible to define a constraint to the topLayoutGuide in the xib editor
// so do it in the code ..
self.selectionContainerTopConstraint = [NSLayoutConstraint constraintWithItem:self.topLayoutGuide
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.selectionContainer
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:0.0f];
[NSLayoutConstraint activateConstraints:@[self.selectionContainerTopConstraint]];
[self createSegmentedViews];
// Observe user interface theme change.
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
[self userInterfaceThemeDidChange];
}];
[self userInterfaceThemeDidChange];
}
- (void)userInterfaceThemeDidChange
{
[ThemeService.theme applyStyleOnNavigationBar:self.navigationController.navigationBar];
self.activityIndicator.backgroundColor = ThemeService.theme.overlayBackgroundColor;
self.view.backgroundColor = ThemeService.theme.backgroundColor;
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return ThemeService.theme.statusBarStyle;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (_selectedViewController)
{
2016-01-29 17:16:30 +00:00
// Make iOS invoke child viewWillAppear
[_selectedViewController beginAppearanceTransition:YES animated:animated];
2016-01-29 17:16:30 +00:00
}
isViewAppeared = YES;
2016-01-29 17:16:30 +00:00
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (_selectedViewController)
{
// Make iOS invoke child viewWillDisappear
[_selectedViewController beginAppearanceTransition:NO animated:animated];
}
isViewAppeared = NO;
2016-01-29 17:16:30 +00:00
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (_selectedViewController)
{
// Make iOS invoke child viewDidAppear
[_selectedViewController endAppearanceTransition];
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if (_selectedViewController)
{
// Make iOS invoke child viewDidDisappear
[_selectedViewController endAppearanceTransition];
}
}
- (void)createSegmentedViews
{
NSMutableArray* labels = [[NSMutableArray alloc] init];
NSUInteger count = viewControllers.count;
for (NSUInteger index = 0; index < count; index++)
{
// create programmatically each label
UILabel *label = [[UILabel alloc] init];
label.text = sectionTitles[index];
label.font = [UIFont systemFontOfSize:17];
label.textAlignment = NSTextAlignmentCenter;
label.textColor = _sectionHeaderTintColor;
label.backgroundColor = [UIColor clearColor];
2017-01-16 15:06:06 +00:00
label.accessibilityIdentifier = [NSString stringWithFormat:@"SegmentedVCSectionLabel%tu", index];
// the constraint defines the label frame
// so ignore any autolayout stuff
[label setTranslatesAutoresizingMaskIntoConstraints:NO];
// add the label before setting the constraints
[self.selectionContainer addSubview:label];
NSLayoutConstraint *leftConstraint;
if (labels.count)
{
leftConstraint = [NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:labels[index - 1]
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0];
}
else
{
leftConstraint = [NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.selectionContainer
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0];
}
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.selectionContainer
attribute:NSLayoutAttributeWidth
multiplier:1.0 / count
constant:0];
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.selectionContainer
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.selectionContainer
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0];
// set the constraints
[NSLayoutConstraint activateConstraints:@[leftConstraint, rightConstraint, topConstraint, heightConstraint]];
UITapGestureRecognizer *labelTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onLabelTouch:)];
[labelTapGesture setNumberOfTouchesRequired:1];
[labelTapGesture setNumberOfTapsRequired:1];
label.userInteractionEnabled = YES;
[label addGestureRecognizer:labelTapGesture];
[labels addObject:label];
}
sectionLabels = labels;
[self addSelectedMarkerView];
[self displaySelectedViewController];
}
- (void)addSelectedMarkerView
{
// Sanity check
NSAssert(sectionLabels.count, @"[SegmentedViewController] addSelectedMarkerView failed - At least one view controller is required");
// create the selected marker view
selectedMarkerView = [[UIView alloc] init];
selectedMarkerView.backgroundColor = _sectionHeaderTintColor;
[selectedMarkerView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.selectionContainer addSubview:selectedMarkerView];
leftMarkerViewConstraint = [NSLayoutConstraint constraintWithItem:selectedMarkerView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:sectionLabels[_selectedIndex]
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:selectedMarkerView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.selectionContainer
attribute:NSLayoutAttributeWidth
multiplier:1.0 / sectionLabels.count
constant:0];
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:selectedMarkerView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.selectionContainer
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:selectedMarkerView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:3];
// set the constraints
[NSLayoutConstraint activateConstraints:@[leftMarkerViewConstraint, widthConstraint, bottomConstraint, heightConstraint]];
}
- (void)displaySelectedViewController
{
// Sanity check
NSAssert(sectionLabels.count, @"[SegmentedViewController] displaySelectedViewController failed - At least one view controller is required");
if (_selectedViewController)
{
NSUInteger index = [viewControllers indexOfObject:_selectedViewController];
if (index != NSNotFound)
{
UILabel* label = sectionLabels[index];
label.font = [UIFont systemFontOfSize:17];
}
[_selectedViewController willMoveToParentViewController:nil];
[_selectedViewController.view removeFromSuperview];
[_selectedViewController removeFromParentViewController];
[NSLayoutConstraint deactivateConstraints:@[displayedVCTopConstraint, displayedVCLeftConstraint, displayedVCWidthConstraint, displayedVCHeightConstraint]];
}
UILabel* label = sectionLabels[_selectedIndex];
label.font = [UIFont boldSystemFontOfSize:17];
// update the marker view position
[NSLayoutConstraint deactivateConstraints:@[leftMarkerViewConstraint]];
leftMarkerViewConstraint = [NSLayoutConstraint constraintWithItem:selectedMarkerView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:sectionLabels[_selectedIndex]
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0];
[NSLayoutConstraint activateConstraints:@[leftMarkerViewConstraint]];
// Set the new selected view controller
_selectedViewController = viewControllers[_selectedIndex];
// Make iOS invoke selectedViewController viewWillAppear when the segmented view is already visible
if (isViewAppeared)
{
[_selectedViewController beginAppearanceTransition:YES animated:YES];
}
[self addChildViewController:_selectedViewController];
[_selectedViewController.view setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.viewControllerContainer addSubview:_selectedViewController.view];
displayedVCTopConstraint = [NSLayoutConstraint constraintWithItem:_selectedViewController.view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.viewControllerContainer
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:0.0f];
displayedVCLeftConstraint = [NSLayoutConstraint constraintWithItem:_selectedViewController.view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.viewControllerContainer
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:0.0f];
displayedVCWidthConstraint = [NSLayoutConstraint constraintWithItem:_selectedViewController.view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.viewControllerContainer
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0];
displayedVCHeightConstraint = [NSLayoutConstraint constraintWithItem:_selectedViewController.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.viewControllerContainer
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0];
[NSLayoutConstraint activateConstraints:@[displayedVCTopConstraint, displayedVCLeftConstraint, displayedVCWidthConstraint, displayedVCHeightConstraint]];
[_selectedViewController didMoveToParentViewController:self];
// Make iOS invoke selectedViewController viewDidAppear when the segmented view is already visible
if (isViewAppeared)
{
[_selectedViewController endAppearanceTransition];
}
}
#pragma mark - Search
- (void)showSearch:(BOOL)animated
{
[super showSearch:animated];
// Show the tabs header
if (animated)
{
[UIView animateWithDuration:.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn
animations:^{
self.selectionContainerHeightConstraint.constant = 44;
[self.view layoutIfNeeded];
}
completion:^(BOOL finished){
}];
}
else
{
self.selectionContainerHeightConstraint.constant = 44;
[self.view layoutIfNeeded];
}
}
- (void)hideSearch:(BOOL)animated
{
[super hideSearch:animated];
// Hide the tabs header
if (animated)
{
[UIView animateWithDuration:.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn
animations:^{
self.selectionContainerHeightConstraint.constant = 0;
[self.view layoutIfNeeded];
}
completion:^(BOOL finished) {
// Go back to the main tab
// Do it at the end of the animation when the tabs header of the SegmentedVC is hidden
// so that the user cannot see the selection bar of this header moving
self.selectedIndex = 0;
self.selectedViewController.view.hidden = NO;
}];
}
else
{
self.selectionContainerHeightConstraint.constant = 0;
[self.view layoutIfNeeded];
// Go back to the recents tab
self.selectedIndex = 0;
self.selectedViewController.view.hidden = NO;
}
}
#pragma mark - touch event
- (void)onLabelTouch:(UIGestureRecognizer*)gestureRecognizer
{
NSUInteger pos = [sectionLabels indexOfObject:gestureRecognizer.view];
// check if there is an update before triggering anything
if ((pos != NSNotFound) && (_selectedIndex != pos))
{
// update the selected index
self.selectedIndex = pos;
}
}
@end