Skip to content

Instantly share code, notes, and snippets.

@ifournight
Last active December 16, 2015 02:09
Show Gist options
  • Save ifournight/5360212 to your computer and use it in GitHub Desktop.
Save ifournight/5360212 to your computer and use it in GitHub Desktop.
SToolkitController and Company class.
//
// STabControl.h
// Xcode Reader
//
// Created by Song Hui 2013-4-15
// Copyright(C)Song Hui. All rights reserved.
#import <UIKit/UIKit.h>
@class STabControl;
@protocol SStabControlDelegate<NSObject>
- (void)tabControl:(STabControl *)tabControl didSelectTabAtIndex:(NSUInteger *)index;
@end
@interface STabControl : UIView
// Tab images for normal/ unselected state.
@property (nonatomic, strong) NSArray *tabImages;
// Tab images for selected state.
// Selected images' size should be the same with normal/ unselected images' respectively.
@property (nonatomic, strong) NSArray *tabSelectedImages;
// All tabs STabControl owned from left to right order.
@property (nonatomic, strong) NSArray *tabs;
// Control's delegate.
@property (nonatomic, weak) id<STabControlDelegate> delegate;
// Designated Initializer
- (id)initWithFrame:(CGRect)frame tabImages:(NSArray *)tabImages tabSelectedImages:(NSArray *)tabSelectedImages;
@end
//
// STabControl.m
// Xcode Reader
//
// Created by Song Hui 2013-4-15
// Copyright(C)Song Hui. All rights reserved.
#import "STabControl.h"
const NSUInteger kSTabControlAffectiveTouchWidth = 44;
@implementation STabControl
- (id)initWithFrame:(CGRect)frame tabImages:(NSArray *)tabImages tabSelectedImages:(NSArray *)tabSelectedImages
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
_tabImages = tabImages;
_tabSelectedImages = tabSelectedImages;
}
return self;
}
- (void)willMoveToSuperview:(UIView *)superview
{
[super willMoveToSuperview];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (self.tabImages.count == 0) {
NSLog(@"STabControl: Tab images empty.");
return;
}
if (self.tabImages.count != self.tabSelectedImages.count) {
NSLog(@"STabControl: Tab images' count and selected images' count not match.");
return;
}
NSUInteger tabWidth = kSTabControlAffectiveTouchWidth;
NSUInteger tabHeight = self.frame.size.height;
NSUInteger horizontalOffset = self.frame.size.width / self.tabImages.count;
NSUInteger x = (horizontalOffset - tabWidth) / 2;
NSMutableArray *tabs = [NSMutableArray alloc] initWithCapacity:self.tabImages.count];
for (NSUInteger i = 0; i < self.tabImages.count; i++) {
UIButton *tab = [UIButton alloc] initWithFrame:CGRectIntegral(CGRectMake(x, 0, tabWidth, tabHeight))];
[tab setImage:self.tabImages[i] forState:UIControlStateNormal];
[tab setImage:self.tabSelectedImages[i] forState:UIControlStateSelected];
[tab addTarget:self action:@selector(tabTouchUpInside:) forControlStates:UIControlEventTouchUpInsdie];
// Calculate tab's image edge insets.
UIImage *tabImage = self.tabImages[i];
CGSize tabImageSize = tabImage.size;
NSUInteger topEdgeInset = floor((tabHeight - tabImageSize.height) / 2);
NSUInteger leftEdgeInset = floor((tabWidth - tabImageSize.width) / 2);
NSUInteger bottomEdgeInset = ceil((tabHeight - tabImageSize.height) / 2);
NSUInteger rightEdgeInset = ceil((tabWidth - tabImageSize.width) / 2);
tab.imageEdgeInsets = UIEdgeInsetsMake(topEdgeInset, leftEdgeInset, bottomEdgeInset, rightEdgeInset);
[self addSubview:tab];
[tabs addObject:tab];
x += horizontalOffset;
}
self.tabs = tabs;
[self selectTab:self.tabs[0] animated:NO];
});
}
- (void)selectTab:(UIButton *)selectedTab animated:(BOOL)animated
{
for (UIButton *tab in self.tabs) {
if (selectedTab == tab) {
tab.selected = YES;
self.selectedIndex = [self.tabs indexOfObject:tab];
} else {
tab.selected = NO;
}
}
}
- (void)tabTouchUpInside:(UIButton *)tab
{
[self selectTab:tab animated:NO];
[self.delegate tabControl:self didSelectTabAtIndex:self.selectedIndex];
}
@end
//
// SToolkitBar.h
// Xcode Reader
//
// Created by Song Hui 2013-4-11
// Copyright(C)Song Hui. All rights reserved.
#import <UIKit/UIKit.h>
@interface SToolkitBar : UIToolbar
// SToolkitBar can have STabControl and UIButton as its items.
// Before assignment, please ensure each item has correct frame and UIViewAutoResizingMask, so SToolkitBar can layout all items appropriately when device rotate and any other reason causing SToolkitBar's geometry change.
// FUTURE UPDATE: When UIViewAutoResizingMask cannot achieve the intended layout request. Update maybe require.
@property (nonatomic, strong) NSArray *barItems;
// Designated Initializer is still initWithFrame:. This is just a continent initial method.
- (id)initWithFrame:(CGRect)frame barItems:(NSArray *)barItems backgroundImage:(UIImage *)backgroundImage shadowImage:(UIImage *)shadowImage;
@end
//
// SToolkitBar.h
// Xcode Reader
//
// Created by Song Hui 2013-4-11
// Copyright(C)Song Hui. All rights reserved.
#import "SToolkitBar.h"
@implementation SToolkitBar
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (id)initWithFrame:(CGRect)frame barItems:(NSArray *)barItems backgroundImage:(UIImage *)backgroundImage shadowImage:(UIImage *)shadowImage
{
self = [self initWithFrame:frame];
if (self) {
_barItem = barItem;
[self setBackgroundImage:backgroundImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
// [self setBackgroundImage:backgroundImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsLandscapePhone];
[self setShadowImage:shadowImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
// [self setShadowImage:shadowImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsLandscapePhone];
self.autoresizingMask = UIViewAutoresizingMaskFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin;
}
return self;
}
- (void)setBarItems:(NSArray *)barItems
{
for (id barItem in _barItems) {
[barItem removeFromSuperView];
}
_barItems = barItems;
for (id barItem in self.barItems) {
if ([barItem isKindOfClass:[STabControl class]]) {
[self addSubview:barItem];
} else ([barItem isKindOfClass:[UIButton class]]) {
[self addSubview:barItem];
} else {
NSLog(@"SToolkitBar: Unacceptable Bar Item Type.");
}
}
}
@end
//
// SToolkitcontroller.h
// Xcode Reader
//
// Created by Song Hui 2013-4-11
// Copyright(C)Song Hui. All rights reserved.
#import <UIKit/UIKit.h>
@interface SToolkitController : UIViewController<STabControlDelegate>
UIKIT_EXTERN NSUInteger kSToolkitControllerWidth;
// Controller's root superview.
// Be responsible for displaying a shadow effect at controller's right border.
@property (nonatomic, strong) UIView *containerView;
// The superview for all children view controller.
@property (nonatomic, strong) UIView *viewControllerTransitionView;
// SToolkitController's children view controller.
@property (nonatomic, strong) NSArray *viewControllers;
// The current selected/ on screen child controller.
@property (nonatomic, strong) UIViewController *selectedViewController;
// Self explained.
@property (nonatomic, assign) NSUInteger selectedIndex;
// The main function bar of SToolkitController, controller use it to switch its children controllers.
// ToolkitBar is a hybrid of UITabbar and Standard UIToolbar.
// It has a STabControl to achieve the tab switch ability, along with that, it also has a setting button.
// Because UIToolbar already can put STabControl and UIButton on itself, toolkitBar will be UIToolbar for now.
// Subclass of UIToolbar may be required in later progress of Xcode Reader Project.
@property (nonatomic, strong) UIToolbar *toolkitBar;
// Tab control located on toolkitBar. Controller use it to switch its children controllers.
@property (nonatomic, strong) STabControl *tabControl;
// Toolkit Bar items will determine what's currently displayed on Toolkit Bar.
@property (nonatomic, strong) NSArray *barItems;
// The root/ default Toolkit bar items.
@property (nonatomic, strong) NSArray *defautBarItems;
- (id)initWithViewControllers:(NSArray *)viewControllers selectedIndex:(NSUInteger)selectedIndex;
@end
@interface UIViewController (SToolkitController)
// If view controller has a SToolkitController as its ancestor, return it. Otherwise, return nil.
// Child view controller of SToolkitController that want to quick access SToolkitController can write an getter method in its implementation.
@property (nonatomic, readonly, weak) UIViewController *toolkitController;
// Child view controller of SToolkitController can override this property's setter method in order to change the Bar items display on its parent' Toolkit Bar.
// The override of setter method only require assign the SToolkitBarItems intended to be displayed on ToolkitBar, SToolkitController will notice the change and update the bar appropriately.
// NOTICE: If not being a child of SToolkitController, don't implement this property.
@property (nonatomic, readwrite, weak) NSArray *toolkitBarItems;
@end
//
// SToolkitcontroller.m
// Xcode Reader
//
// Created by Song Hui 2013-4-11
// Copyright(C)Song Hui. All rights reserved.
#import "SToolkitController.h"
const NSUInteger kSToolkitControllerWidth = 320;
const NSUInteger kSToolkitBarHeight = 44;
const UIEdgeInsets kSToolkitBarBackgroundImageCapInsets = UIEdgeInsetsZero; //Placeholder Value
const UIEdgeInsets kSToolkitBarShadowImageCapInsets = UIEdgeInsetsZero; //Placeholder Value
const UIEdgeInsets kSToolkitBarButtonBackgroundImageCapInsets = UIEdgeInsetsZero; //Placeholder Value
const CGRect kTabControlFrame = CGRectMake(40, 0, 180, 44);
const CGRect kSettingButtonFrame = CGRectMake(0, 0, 0, 0); //Placeholder Value
const NSString *kSToolkitBarBackgroundImageName = @"ToolkitBar-Background.png";
const NSString *kSToolkitBarShadowImageName = @"ToolkitBar-Shadow.png";
const NSString *kSTabControlSearchImageName = @"STabControl-Search.png";
const NSString *kSTabControlSearchSelectedImageName = @"STabControl-Search-Selected.png";
const NSString *kSTabControlExploreImageName = @"STabControl-Explore.png";
const NSString *kSTabControlExploreSelectedImageName = @"STabControl-Explore-Selected.png";
const NSString *kSTabControlBookmarkImageName = @"STabControl-Bookmark.png";
const NSString *kSTabControlBookmarkSelectedImageName = @"STabControl-Bookmark-Selected.png";
const NSString *kSToolkitBarButtonImageName = @"ToolkitBar-Button.png";
const NSString *kSToolkitBarButtonBackgroundImageName = @"ToolkitBar-Button-Background.png"
const NSString *kSToolkitBarButtonBackgroundSelectedImageName = @"ToolkitBar-Button-BackgroundSelected.png"
typedef NS_ENUM(NSInteger, SToolkitTransitionAnimation) {
SToolkitTransitionAnimationNone,
SToolkitTransitionAnimationPlaceholder1,
SToolkitTransitionAnimationPlaceholder2
};
@interface SToolkitController : UIViewController()
// Child view controller's transition will be added to this queue. And execute FIFO.
@property (nonatomic, strong) NSArray *transitionQueue;
@end
@implementation SToolkitController
#pragma mark - Life Cycle
- (id)initWithViewControllers:(NSArray *)viewControllers selectedIndex:(NSUInteger)selectedIndex
{
self = [super init];
if (self) {
_viewControllers = viewControllers;
_selectedIndex = selectedIndex;
_transitionQueue = [[NSMutableArray alloc] init];
// Default Bar Items Creation
UIImage *tabControlSearchImage = [UIImage imageNamed:kSTabControlSearchImageName];
UIImage *tabControlExploreImage = [UIImage imageNamed:kSTabControlExploreImageName];
UIImage *tabControlBookmarkImage = [UIImage imageNamed:kSTabControlBookmarkImageName];
UIImage *tabControlSearchSelectedImage = [UIImage imageNamed:kSTabControlSearchSelectedImageName];
UIImage *tabControlExploreSelectedImage = [UIImage imageNamed:kSTabControlExploreSelectedImageName];
UIImage *tabControlBookmarkSelectedImage = [UIImage imageNamed:kSTabControlBookmarkSelectedImageName];
STabControl *tabControl = [[STabControl alloc] initWithFrame:kTabControlFrame tabImages:@[tabControlSearchImage, tabControlExploreImage, tabControlBookmarkImage] tabSelectedImages:@[tabControlSearchSelectedImage, tabControlExploreSelectedImage, tabControlBookmarkSelectedImage]];
tabControl.delegate = self;
UIButton *settingButton = [[UIButton alloc] initWithFrame:kSettingButtonFrame];
UIImage *settingButtonImage = [UIImage imageNamed:kSToolkitBarButtonImageName];
UIImage *settingButtonBackgroundImage = [[UIImage imageNamed:kSToolkitBarButtonBackgroundImageName] resizableImageWithCapInsets:kSToolkitBarButtonBackgroundImageCapInsets];
UIImage *settingButtonBackgroundSelectedImage = [[UIImage imageNamed:kSToolkitBarButtonBackgroundSelectedImageName] resizableImageWithCapInsets:kSToolkitBarButtonBackgroundImageCapInsets];
[settingButton setImage:settingButtonImage forState:UIControlStateNormal];
[settingButton setBackgroundImage:settingButtonBackgroundImage forState:UIControlStateNormal];
[settingButton setBackgroundImage:settingButtonBackgroundSelectedImage forState:UIControlStateHighlighted | UIControlStateSelected];
[settingButton addTarget:self action:@selector(settingButtonTouchUpInside:) forControlEvent:UIControlEventTouchUpInside];
NSArray *defaultBarItems = @[tabControl, settingButton];
_defaultBarItems = defaultBarItems;
_barItems = defaultBarItems;
}
return self;
}
- (void)loadView
{
CGSize applicationSize = [[UIScreen mainScreen] applicationFrame].size;
CGFloat applicationHeight = applicationSize.height;
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
applicationHeight = applicationSize.width;
}
// Alloc and init views
_containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kSToolkitControllerWidth, applicationHeight)];
_viewControllerTransitionView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kSToolkitControllerWidth, applicationHeight - kSToolkitBarHeight)];
UIImage *toolkitBarBackgroundImage = [[UIImage imageNamed:kSToolkitBarBackgroundImageName] resizableImageWithCapInsets:kSToolkitBarBackgroundImageCapInsets];
UIImage *toolkitBarShadowImage = [[UIImage imageNamed:kSToolkitBarShadowImageName] resizableImageWithCapInsets:kSToolkitBarShadowImageCapInsets];
_toolkitBar = [[SToolkitBar alloc] initWithFrame:CGRectMake(0, applicationHeight - kSToolkitBarHeight, kSToolkitControllerWidth, kSToolkitBarHeight) barItems:self.barItems backgroundImage:toolkitBarBackgroundImage shadowImage:toolkitBarShadowImage];
// Views's autoresizingMask configuration.
_containerView.autoresizingMask = UIViewAutoresizingMaskFlexibleHeight | UIViewAutoresizingMaskFlexibleRightMargin | UIViewAutoresizingMaskFlexibleBottomMargin;
_viewControllerTransitionView.autoresizingMask = UIViewAutoresizingMaskFlexibleHeight | UIViewAutoresizingMaskFlexibleRightMargin | UIViewAutoresizingMaskFlexibleBottomMargin;
_toolkitBar.autoresizingMask = UIViewAutoresizingMaskFlexibleTopMargin | UIViewAutoresizingMaskFlexibleRightMargin;
// Add Subviews
self.view = _containerView;
[_containerView addSubview:_viewControllerTransitionView];
[_containerView addSubView:_toolkitBar];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Children view controllers' toolkitBatItem observing.
for (UIViewController *viewController in self.viewController) {
[viewController addObserver:self forKeyPath:@"toolkitBatItems" options:NSKeyValueObservingNew context:nil];
}
}
#pragma mark - Children View Controllers Management and Transition
- (void)selectViewControllerAtIndex:(NSUInteger)selectedIndex animationType:(SToolkitTransitionAnimation)animationType
{
if (!self.selectedViewController || self.selectedIndex != selectedIndex) {
NSArray *transition = @[@self.selectedIndex, @selectedIndex];
// Add transition to transition queue.
if (self.transitionQueue.count == 0) {
[self.transitionQueue addObject:transition];
// Only immediately start transition if queue is empty.
[self executeTransitionWithAnimationType:animationType];
} else {
[self.transitionQueue addObject:transition];
}
}
}
- (void)executeTransitionWithAnimationType:(SToolkitTransitionAnimation)animationType
{
NSArray *transition = self.transitionQueue[0];
NSUInteger fromIndex = [transition[0] integerValue];
NSUInteger toIndex = [transition[1] integerValue];
UIViewController *fromController = self.viewControllers[fromIndex];
UIViewController *toController = self.viewControllers[toIndex];
if (animationType == SToolkitTransitionAnimationNone) {
toController.view.frame = CGRectMake(0, 0, self.viewControllerTransitionView.bounds.size.width, self.viewControllerTransitionView.bounds.size.height);
if (self.selectedViewController && self.selectedViewController == fromController) {
[self.selectedViewController removeFromParentViewController];
[self.selectedViewController beginAppearanceTransition:NO animated:NO];
[self.selectedViewController.view removeFromSuperview];
[self.selectedViewController endAppearanceTransition];
}
[self addChildViewController:toController];
[toController beginAppearanceTransition:YES animated:NO];
[self.viewControllerTransitionView addSubview:toController.view];
[toController endAppearanceTransition];
} else if (animationType == SToolkitTransitionAnimationPlaceHolder1) {
}
self.selectedIndex = toIndex;
self.selectedViewController = toController;
// Queue out transition and execute next one if there is.
[self.transitionQueue removeObject:transition];
if (self.transitionQueue.count != 0) {
[self executeTransitionWithAnimationType:SToolkitTransitionAnimationNone];
}
}
- (void)tabControl:(STabControl *)tabControl didSelectTabAtIndex:(NSUInteger)index
{
[self selectViewControllerAtIndex:index animationType:SToolkitTransitionAnimationNone];
}
#pragma mark - Toolkit Bar Items Update
- (void)observeForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"toolkitBarItems"] && object == self.selectedViewController) {
self.barItems = self.selectedViewController.toolkitBarItems;
self.tookitBar.barItems = self.barItems;
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment