diff --git a/Limelight.xcodeproj/project.pbxproj b/Limelight.xcodeproj/project.pbxproj index 0c12d335..2d7ee76c 100644 --- a/Limelight.xcodeproj/project.pbxproj +++ b/Limelight.xcodeproj/project.pbxproj @@ -48,6 +48,11 @@ FB8946EC19F6AFE400339C8A /* libssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB8946E119F6AFB800339C8A /* libssl.a */; }; FB8946ED19F6AFE800339C8A /* libopus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB8946EA19F6AFB800339C8A /* libopus.a */; }; FBD3494319FC9C04002D2A60 /* AppManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD3494219FC9C04002D2A60 /* AppManager.m */; }; + FBD3495019FF2174002D2A60 /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD3494F19FF2174002D2A60 /* SettingsViewController.m */; }; + FBD3495319FF36FB002D2A60 /* SWRevealViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD3495219FF36FB002D2A60 /* SWRevealViewController.m */; }; + FBD3495B1A004411002D2A60 /* Hosts.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD3495A1A004411002D2A60 /* Hosts.m */; }; + FBD3495E1A004412002D2A60 /* Settings.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD3495D1A004412002D2A60 /* Settings.m */; }; + FBD349621A0089F6002D2A60 /* DataManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD349611A0089F6002D2A60 /* DataManager.m */; }; FBDE86E019F7A837001C18A8 /* UIComputerView.m in Sources */ = {isa = PBXBuildFile; fileRef = FBDE86DF19F7A837001C18A8 /* UIComputerView.m */; }; FBDE86E619F82297001C18A8 /* UIAppView.m in Sources */ = {isa = PBXBuildFile; fileRef = FBDE86E519F82297001C18A8 /* UIAppView.m */; }; FBDE86E919F82315001C18A8 /* App.m in Sources */ = {isa = PBXBuildFile; fileRef = FBDE86E819F82315001C18A8 /* App.m */; }; @@ -217,6 +222,17 @@ FB8946EA19F6AFB800339C8A /* libopus.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libopus.a; sourceTree = ""; }; FBD3494119FC9C04002D2A60 /* AppManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppManager.h; sourceTree = ""; }; FBD3494219FC9C04002D2A60 /* AppManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppManager.m; sourceTree = ""; }; + FBD3494E19FF2174002D2A60 /* SettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsViewController.h; sourceTree = ""; }; + FBD3494F19FF2174002D2A60 /* SettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsViewController.m; sourceTree = ""; }; + FBD3495119FF36FB002D2A60 /* SWRevealViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SWRevealViewController.h; sourceTree = ""; }; + FBD3495219FF36FB002D2A60 /* SWRevealViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SWRevealViewController.m; sourceTree = ""; }; + FBD349571A003F05002D2A60 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; + FBD349591A004411002D2A60 /* Hosts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Hosts.h; path = Database/Hosts.h; sourceTree = ""; }; + FBD3495A1A004411002D2A60 /* Hosts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Hosts.m; path = Database/Hosts.m; sourceTree = ""; }; + FBD3495C1A004412002D2A60 /* Settings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Settings.h; path = Database/Settings.h; sourceTree = ""; }; + FBD3495D1A004412002D2A60 /* Settings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Settings.m; path = Database/Settings.m; sourceTree = ""; }; + FBD349601A0089F6002D2A60 /* DataManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DataManager.h; path = Database/DataManager.h; sourceTree = ""; }; + FBD349611A0089F6002D2A60 /* DataManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DataManager.m; path = Database/DataManager.m; sourceTree = ""; }; FBDE86DE19F7A837001C18A8 /* UIComputerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIComputerView.h; sourceTree = ""; }; FBDE86DF19F7A837001C18A8 /* UIComputerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIComputerView.m; sourceTree = ""; }; FBDE86E419F82297001C18A8 /* UIAppView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIAppView.h; sourceTree = ""; }; @@ -289,6 +305,7 @@ FB290CF019B2C406004C83CF /* Frameworks */ = { isa = PBXGroup; children = ( + FBD349571A003F05002D2A60 /* libsqlite3.dylib */, FB89468F19F6AFB800339C8A /* libs */, FB7E794319C8B71B00A15F68 /* libiconv.dylib */, FB290DC319B2E98F004C83CF /* libxml2.dylib */, @@ -311,6 +328,7 @@ FB89461519F646E200339C8A /* Stream */, FB89461E19F646E200339C8A /* Utility */, FB89462319F646E200339C8A /* ViewControllers */, + FBD3495F1A004453002D2A60 /* Database */, FB290CFA19B2C406004C83CF /* Supporting Files */, FB290D0219B2C406004C83CF /* AppDelegate.h */, FB290D0319B2C406004C83CF /* AppDelegate.m */, @@ -424,10 +442,14 @@ FB89462319F646E200339C8A /* ViewControllers */ = { isa = PBXGroup; children = ( + FBD3495119FF36FB002D2A60 /* SWRevealViewController.h */, + FBD3495219FF36FB002D2A60 /* SWRevealViewController.m */, FB89462419F646E200339C8A /* MainFrameViewController.h */, FB89462519F646E200339C8A /* MainFrameViewController.m */, FB89462619F646E200339C8A /* StreamFrameViewController.h */, FB89462719F646E200339C8A /* StreamFrameViewController.m */, + FBD3494E19FF2174002D2A60 /* SettingsViewController.h */, + FBD3494F19FF2174002D2A60 /* SettingsViewController.m */, ); path = ViewControllers; sourceTree = ""; @@ -593,6 +615,19 @@ path = lib; sourceTree = ""; }; + FBD3495F1A004453002D2A60 /* Database */ = { + isa = PBXGroup; + children = ( + FBD3495C1A004412002D2A60 /* Settings.h */, + FBD3495D1A004412002D2A60 /* Settings.m */, + FBD349591A004411002D2A60 /* Hosts.h */, + FBD3495A1A004411002D2A60 /* Hosts.m */, + FBD349601A0089F6002D2A60 /* DataManager.h */, + FBD349611A0089F6002D2A60 /* DataManager.m */, + ); + name = Database; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -713,6 +748,7 @@ files = ( FB290D0719B2C406004C83CF /* Limelight.xcdatamodeld in Sources */, FB89463219F646E200339C8A /* VideoDecoderRenderer.m in Sources */, + FBD3495E1A004412002D2A60 /* Settings.m in Sources */, FB290D0419B2C406004C83CF /* AppDelegate.m in Sources */, FB89463419F646E200339C8A /* Utils.m in Sources */, FBDE86E619F82297001C18A8 /* UIAppView.m in Sources */, @@ -722,9 +758,12 @@ FBDE86E019F7A837001C18A8 /* UIComputerView.m in Sources */, FBDE86E919F82315001C18A8 /* App.m in Sources */, FB89463019F646E200339C8A /* StreamConfiguration.m in Sources */, + FBD3495319FF36FB002D2A60 /* SWRevealViewController.m in Sources */, + FBD3495019FF2174002D2A60 /* SettingsViewController.m in Sources */, FB89462C19F646E200339C8A /* HttpManager.m in Sources */, FB89462D19F646E200339C8A /* MDNSManager.m in Sources */, FB89462B19F646E200339C8A /* StreamView.m in Sources */, + FBD3495B1A004411002D2A60 /* Hosts.m in Sources */, FB89463519F646E200339C8A /* MainFrameViewController.m in Sources */, FB89463619F646E200339C8A /* StreamFrameViewController.m in Sources */, FB89462819F646E200339C8A /* CryptoManager.m in Sources */, @@ -732,6 +771,7 @@ FB290D0019B2C406004C83CF /* main.m in Sources */, FBD3494319FC9C04002D2A60 /* AppManager.m in Sources */, FB89462A19F646E200339C8A /* ControllerSupport.m in Sources */, + FBD349621A0089F6002D2A60 /* DataManager.m in Sources */, FB89463119F646E200339C8A /* StreamManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Limelight/AppDelegate.h b/Limelight/AppDelegate.h index 3e323b47..8d06c523 100644 --- a/Limelight/AppDelegate.h +++ b/Limelight/AppDelegate.h @@ -18,5 +18,6 @@ - (void)saveContext; - (NSURL *)applicationDocumentsDirectory; +- (NSURL*) getStoreURL; @end diff --git a/Limelight/AppDelegate.m b/Limelight/AppDelegate.m index 680aa5f9..18fabba9 100644 --- a/Limelight/AppDelegate.m +++ b/Limelight/AppDelegate.m @@ -100,7 +100,7 @@ static NSOperationQueue* mainQueue; return _persistentStoreCoordinator; } - NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Limelight_iOS.sqlite"]; + NSURL *storeURL = [self getStoreURL]; NSError *error = nil; _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; @@ -143,4 +143,8 @@ static NSOperationQueue* mainQueue; return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; } +- (NSURL*) getStoreURL { + return [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Limelight_iOS.sqlite"]; +} + @end diff --git a/Limelight/Database/DataManager.h b/Limelight/Database/DataManager.h new file mode 100644 index 00000000..3a193be5 --- /dev/null +++ b/Limelight/Database/DataManager.h @@ -0,0 +1,22 @@ +// +// DataManager.h +// Limelight +// +// Created by Diego Waxemberg on 10/28/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import +#import "Settings.h" +#import "AppDelegate.h" +#import "Hosts.h" + +@interface DataManager : NSObject + +@property (strong, nonatomic) AppDelegate* appDelegate; + +- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width; +- (Settings*) retrieveSettings; + + +@end diff --git a/Limelight/Database/DataManager.m b/Limelight/Database/DataManager.m new file mode 100644 index 00000000..16130224 --- /dev/null +++ b/Limelight/Database/DataManager.m @@ -0,0 +1,65 @@ +// +// DataManager.m +// Limelight +// +// Created by Diego Waxemberg on 10/28/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import "DataManager.h" + +@implementation DataManager +static NSInteger DEFAULT_BITRATE = 10000; +static NSInteger DEFAULT_FRAMERATE = 60; +static NSInteger DEFAULT_HEIGHT = 720; +static NSInteger DEFAULT_WIDTH = 1280; + +- (id) init { + self = [super init]; + self.appDelegate = [[UIApplication sharedApplication] delegate]; + return self; +} + +- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width { + Settings* settingsToSave = [self retrieveSettings]; + settingsToSave.framerate = [NSNumber numberWithInteger:framerate]; + settingsToSave.bitrate = [NSNumber numberWithInteger:bitrate]; + settingsToSave.height = [NSNumber numberWithInteger:height]; + settingsToSave.width = [NSNumber numberWithInteger:width]; + [self saveSettings:settingsToSave]; +} + +- (Settings*) retrieveSettings { + NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init]; + NSEntityDescription* entity = [NSEntityDescription entityForName:@"Settings" inManagedObjectContext:[self.appDelegate managedObjectContext]]; + [fetchRequest setEntity:entity]; + [fetchRequest setAffectedStores:[NSArray arrayWithObjects:[[self.appDelegate persistentStoreCoordinator] persistentStoreForURL:[self.appDelegate getStoreURL]], nil]]; + + NSError* error; + NSArray* fetchedRecords = [[self.appDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error]; + + if (fetchedRecords.count == 0) { + // create a new settings object with the default values + Settings* settings = [[Settings alloc] initWithEntity:entity insertIntoManagedObjectContext:[self.appDelegate managedObjectContext]]; + settings.framerate = [NSNumber numberWithInteger:DEFAULT_FRAMERATE]; + settings.bitrate = [NSNumber numberWithInteger:DEFAULT_BITRATE]; + settings.height = [NSNumber numberWithInteger:DEFAULT_HEIGHT]; + settings.width = [NSNumber numberWithInteger:DEFAULT_WIDTH]; + return settings; + } else { + // we should only ever have 1 settings object stored + return [fetchedRecords objectAtIndex:0]; + } +} + +- (void) saveSettings:(Settings*)settings { + NSError* error; + if (![[self.appDelegate managedObjectContext] save:&error]) { + NSLog(@"ERROR: Unable to save settings to database"); + } + + [self.appDelegate saveContext]; +} + + +@end diff --git a/Limelight/Database/Hosts.h b/Limelight/Database/Hosts.h new file mode 100644 index 00000000..2b4d1843 --- /dev/null +++ b/Limelight/Database/Hosts.h @@ -0,0 +1,18 @@ +// +// Hosts.h +// Limelight +// +// Created by Diego Waxemberg on 10/28/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import +#import + + +@interface Hosts : NSManagedObject + +@property (nonatomic, retain) NSString * address; +@property (nonatomic, retain) NSString * name; + +@end diff --git a/Limelight/Database/Hosts.m b/Limelight/Database/Hosts.m new file mode 100644 index 00000000..71eeaa60 --- /dev/null +++ b/Limelight/Database/Hosts.m @@ -0,0 +1,17 @@ +// +// Hosts.m +// Limelight +// +// Created by Diego Waxemberg on 10/28/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import "Hosts.h" + + +@implementation Hosts + +@dynamic address; +@dynamic name; + +@end diff --git a/Limelight/Database/Settings.h b/Limelight/Database/Settings.h new file mode 100644 index 00000000..1198a2c0 --- /dev/null +++ b/Limelight/Database/Settings.h @@ -0,0 +1,20 @@ +// +// Settings.h +// Limelight +// +// Created by Diego Waxemberg on 10/28/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import +#import + + +@interface Settings : NSManagedObject + +@property (nonatomic, retain) NSNumber * bitrate; +@property (nonatomic, retain) NSNumber * framerate; +@property (nonatomic, retain) NSNumber * height; +@property (nonatomic, retain) NSNumber * width; + +@end diff --git a/Limelight/Database/Settings.m b/Limelight/Database/Settings.m new file mode 100644 index 00000000..001f8ca2 --- /dev/null +++ b/Limelight/Database/Settings.m @@ -0,0 +1,19 @@ +// +// Settings.m +// Limelight +// +// Created by Diego Waxemberg on 10/28/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import "Settings.h" + + +@implementation Settings + +@dynamic bitrate; +@dynamic framerate; +@dynamic height; +@dynamic width; + +@end diff --git a/Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2-1.jpg b/Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2-1.jpg deleted file mode 100644 index ffd4f4af..00000000 Binary files a/Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2-1.jpg and /dev/null differ diff --git a/Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2-2.jpg b/Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2-2.jpg deleted file mode 100644 index ffd4f4af..00000000 Binary files a/Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2-2.jpg and /dev/null differ diff --git a/Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2.jpg b/Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2.jpg deleted file mode 100644 index ffd4f4af..00000000 Binary files a/Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2.jpg and /dev/null differ diff --git a/Limelight/Images.xcassets/Left4Dead2.imageset/Contents.json b/Limelight/Images.xcassets/Settings.imageset/Contents.json similarity index 59% rename from Limelight/Images.xcassets/Left4Dead2.imageset/Contents.json rename to Limelight/Images.xcassets/Settings.imageset/Contents.json index 52fbbb2c..00e65010 100644 --- a/Limelight/Images.xcassets/Left4Dead2.imageset/Contents.json +++ b/Limelight/Images.xcassets/Settings.imageset/Contents.json @@ -2,18 +2,16 @@ "images" : [ { "idiom" : "universal", - "scale" : "1x", - "filename" : "Left4Dead2-2.jpg" + "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", - "filename" : "Left4Dead2.jpg" + "filename" : "settings_2x.png" }, { "idiom" : "universal", - "scale" : "3x", - "filename" : "Left4Dead2-1.jpg" + "scale" : "3x" } ], "info" : { diff --git a/Limelight/Images.xcassets/Settings.imageset/settings_2x.png b/Limelight/Images.xcassets/Settings.imageset/settings_2x.png new file mode 100644 index 00000000..bb5b00c9 Binary files /dev/null and b/Limelight/Images.xcassets/Settings.imageset/settings_2x.png differ diff --git a/Limelight/Limelight.xcdatamodeld/Limelight.xcdatamodel/contents b/Limelight/Limelight.xcdatamodeld/Limelight.xcdatamodel/contents index 193f33c9..4012f03c 100644 --- a/Limelight/Limelight.xcdatamodeld/Limelight.xcdatamodel/contents +++ b/Limelight/Limelight.xcdatamodeld/Limelight.xcdatamodel/contents @@ -1,4 +1,17 @@ - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Limelight/ViewControllers/MainFrameViewController.h b/Limelight/ViewControllers/MainFrameViewController.h index 70e106eb..cc8a15b6 100644 --- a/Limelight/ViewControllers/MainFrameViewController.h +++ b/Limelight/ViewControllers/MainFrameViewController.h @@ -13,8 +13,10 @@ #import "UIComputerView.h" #import "UIAppView.h" #import "AppManager.h" +#import "SWRevealViewController.h" -@interface MainFrameViewController : UIViewController +@interface MainFrameViewController : UIViewController +@property (strong, nonatomic) IBOutlet UIBarButtonItem *settingsSidebarButton; + (StreamConfiguration*) getStreamConfiguration; diff --git a/Limelight/ViewControllers/MainFrameViewController.m b/Limelight/ViewControllers/MainFrameViewController.m index 328b0f18..fe6d06c6 100644 --- a/Limelight/ViewControllers/MainFrameViewController.m +++ b/Limelight/ViewControllers/MainFrameViewController.m @@ -16,6 +16,9 @@ #import "UIComputerView.h" #import "UIAppView.h" #import "App.h" +#import "SettingsViewController.h" +#import "DataManager.h" +#import "Settings.h" @implementation MainFrameViewController { NSOperationQueue* _opQueue; @@ -134,60 +137,58 @@ static StreamConfiguration* streamConfig; return; } - // TODO: actually allow the user to choose the config - unsigned long selectedConf = 1; - NSLog(@"selectedConf: %ld", selectedConf); - switch (selectedConf) { - case 0: - streamConfig.width = 1280; - streamConfig.height = 720; - streamConfig.frameRate = 30; - streamConfig.bitRate = 5000; - break; - default: - case 1: - streamConfig.width = 1280; - streamConfig.height = 720; - streamConfig.frameRate = 60; - streamConfig.bitRate = 10000; - break; - case 2: - streamConfig.width = 1920; - streamConfig.height = 1080; - streamConfig.frameRate = 30; - streamConfig.bitRate = 10000; - break; - case 3: - streamConfig.width = 1920; - streamConfig.height = 1080; - streamConfig.frameRate = 60; - streamConfig.bitRate = 20000; - break; + DataManager* dataMan = [[DataManager alloc] init]; + Settings* streamSettings = [dataMan retrieveSettings]; + + streamConfig.frameRate = [streamSettings.framerate intValue]; + streamConfig.bitRate = [streamSettings.bitrate intValue]; + streamConfig.height = [streamSettings.height intValue]; + streamConfig.width = [streamSettings.width intValue]; + + [self performSegueWithIdentifier:@"createStreamFrame" sender:nil]; +} + +- (void)revealController:(SWRevealViewController *)revealController didMoveToPosition:(FrontViewPosition)position { + // If we moved back to the center position, we should save the settings + if (position == FrontViewPositionLeft) { + [(SettingsViewController*)[revealController rearViewController] saveSettings]; } - NSLog(@"StreamConfig: %@, %d, %dx%dx%d at %d Mbps", streamConfig.host, streamConfig.hostAddr, streamConfig.width, streamConfig.height, streamConfig.frameRate, streamConfig.bitRate); - [self performSegueWithIdentifier:@"createStreamFrame" sender:self]; } - (void)viewDidLoad { [super viewDidLoad]; - - NSArray* streamConfigVals = [[NSArray alloc] initWithObjects:@"1280x720 (30Hz)", @"1280x720 (60Hz)", @"1920x1080 (30Hz)", @"1920x1080 (60Hz)",nil]; + + // Change button color + _settingsSidebarButton.tintColor = [UIColor colorWithRed:.2 green:.9 blue:0.f alpha:1.f]; + + // Set the side bar button action. When it's tapped, it'll show up the sidebar. + _settingsSidebarButton.target = self.revealViewController; + _settingsSidebarButton.action = @selector(revealToggle:); + + // Set the gesture + [self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer]; + + [self.revealViewController setDelegate:self]; + + //NSArray* streamConfigVals = [[NSArray alloc] initWithObjects:@"1280x720 (30Hz)", @"1280x720 (60Hz)", @"1920x1080 (30Hz)", @"1920x1080 (60Hz)",nil]; _opQueue = [[NSOperationQueue alloc] init]; [CryptoManager generateKeyPairUsingSSl]; _uniqueId = [CryptoManager getUniqueID]; _cert = [CryptoManager readCertFromFile]; - // Initialize the host picker list + // Only initialize the host picker list once if (hostList == nil) { hostList = [[NSMutableSet alloc] init]; } + [self setAutomaticallyAdjustsScrollViewInsets:NO]; + hostScrollView = [[UIScrollView alloc] init]; - hostScrollView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height / 2); + hostScrollView.frame = CGRectMake(0, self.navigationController.navigationBar.frame.origin.y + self.navigationController.navigationBar.frame.size.height, self.view.frame.size.width, self.view.frame.size.height / 2); [hostScrollView setShowsHorizontalScrollIndicator:NO]; - + appScrollView = [[UIScrollView alloc] init]; appScrollView.frame = CGRectMake(0, hostScrollView.frame.size.height, self.view.frame.size.width, self.view.frame.size.height / 2); [appScrollView setShowsHorizontalScrollIndicator:NO]; @@ -199,7 +200,8 @@ static StreamConfiguration* streamConfig; - (void)viewDidAppear:(BOOL)animated { - [super viewDidDisappear:animated]; + [super viewDidAppear:animated]; + [self.navigationController setNavigationBarHidden:NO animated:YES]; _mDNSManager = [[MDNSManager alloc] initWithCallback:self]; [_mDNSManager searchForHosts]; } diff --git a/Limelight/ViewControllers/SWRevealViewController.h b/Limelight/ViewControllers/SWRevealViewController.h new file mode 100755 index 00000000..279106e3 --- /dev/null +++ b/Limelight/ViewControllers/SWRevealViewController.h @@ -0,0 +1,418 @@ +/* + + Copyright (c) 2013 Joan Lluch + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is furnished + to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + Early code inspired on a similar class by Philip Kluz (Philip.Kluz@zuui.org) + +*/ + +/* + + RELEASE NOTES + + + Version 2.3.0 (Current Version) + + - StoryBoard initializing bug fix + - Minor Code refactoring + + Version 2.2.0 + + - State Restoration support. + - Reverted panGestureRecognizer implementation to before v2.1.0 (works better). + - New properties 'toggleAnimationType', 'springDampingRatio'. Default reveal animation is 'Spring' + - New property 'frontViewShadowColor' + - New properties 'clipsViewsToBounds' and '_extendedPointInsideHit' + - New delegate methods for finer control of front view location in the overdraw area, as long as deprecation note on former delegate methods + - Other minor changes that should not affect current implementations + + Version 2.1.0 + + - Removed SWDirectionPanGestureRecognizer. Horizontal panning is filtered on the shouldBegin delegate. This is cleaner, I hope it does not break previous funcionality + - Took a cleaner approach to storyboard support. SWRevealViewControllerSegue is now deprecated and you should use SWRevealViewControllerSegueSetController and SWRevealViewControllerSeguePushController instead. + - A minor change on the autoresizingMask of the internal views to fix a glitch on iOS8. This should not affect iOS7 + + Version 2.0.2 + + - Added new delegates for better control of gesture recognizers + + Version 2.0.1 + + - Fix: draggableBorderWidth now correctly handles the cases where one of the rear controllers is not provided + - Fix: the shadow related properties are now granted at any time after view load, not just after initialization. + + Version 2.0.0 + +- Dropped support for iOS6 and earlier. This version will only work on iOS7 + +- The method setFrontViewController:animated: does not longer perform a full reveal animation. Instead it just replaces the frontViewController in + its current position. Use the new pushFrontViewController:animated: method to perform a replacement of the front controlles with reveal animation + as in the previous version + + IMPORTANT: You must replace all calls to setFrontViewController:animated by calls to pushFrontViewController:animated to prevent breaking + functionality on existing projects. + +- Added support for animated replacement of child controllers: setRearViewController, setFrontViewController, setRightViewController now have animated versions. + +- The new 'replaceViewAnimationDuration' property sets the default duration of child viewController replacement. + +- Added the following new delegate methods + revealController:willAddViewController:forOperation:animated: + revealController:didAddViewController:forOperation:animated: + +- The class also supports custom UIViewControllerAnimatedTransitioning related with the replacement of child viewControllers. + You can implement the following new delegate method: revealController:animationControllerForOperation:fromViewController:toViewController: + and provide an object conforming to UIViewControllerAnimatedTransitioning to implement custom animations. + + Version 1.1.3 + +- Reverted the supportedInterfaceOrientations to the default behavior. This is consistent with Apple provided controllers + +- The presentFrontViewHierarchically now dynamically takes into account the smaller header height of bars on iPhone landscape orientation + + Version 1.1.2 + + - The status bar style and appearance are now handled in sync with the class animations. + You can implement the methods preferredStatusBarStyle and prefersStatusBarHidden on your child controllers to define the desired appearance + + - The loadView method now calls a method, loadStoryboardControllers, just for the purpose of loading child controllers from a storyboard. + You can override this method and remove the @try @catch statements if you want the debugger not to stop at them in case you have set an exception breakpoint. + + Version 1.1.1 + + - You can now get a tapGestureRecognizer from the class. See the tapGestureRecognizer method for more information. + + - Both the panGestureRecognizer and the tapGestureRecognizer are now attached to the revealViewController's front content view + by default, so they will start working just by calling their access methods even if you do not attach them to any of your views. + This enables you to dissable interactions on your views -for example based on position- without breaking normal gesture behavior. + + - Corrected a bug that caused a crash on iOS6 and earlier. + + Version 1.1.0 + + - The method setFrontViewController:animated now performs the correct animations both for left and right controllers. + + - The class now automatically handles the status bar appearance depending on the currently shown child controller. + + Version 1.0.8 + + - Support for constant width frontView by setting a negative value to reveal widths. See properties rearViewRevealWidth and rightViewRevealWidth + + - Support for draggableBorderWidth. See property of the same name. + + - The Pan gesture recongnizer can be disabled by implementing the following delegate method and returning NO + revealControllerPanGestureShouldBegin: + + - Added the ability to track pan gesture reveal progress through the following new delegate methods + revealController:panGestureBeganFromLocation:progress: + revealController:panGestureMovedToLocation:progress: + revealController:panGestureEndedToLocation:progress: + + Previous Versions + + - No release notes were updated for previous versions. + +*/ + + +#import + +@class SWRevealViewController; +@protocol SWRevealViewControllerDelegate; + +#pragma mark - SWRevealViewController Class + +// Enum values for setFrontViewPosition:animated: +typedef NS_ENUM( NSInteger, FrontViewPosition) +{ + // Front controller is removed from view. Animated transitioning from this state will cause the same + // effect than animating from FrontViewPositionLeftSideMost. Use this instead of FrontViewPositionLeftSideMost when + // you want to remove the front view controller view from the view hierarchy. + FrontViewPositionLeftSideMostRemoved, + + // Left most position, front view is presented left-offseted by rightViewRevealWidth+rigthViewRevealOverdraw + FrontViewPositionLeftSideMost, + + // Left position, front view is presented left-offseted by rightViewRevealWidth + FrontViewPositionLeftSide, + + // Center position, rear view is hidden behind front controller + FrontViewPositionLeft, + + // Right possition, front view is presented right-offseted by rearViewRevealWidth + FrontViewPositionRight, + + // Right most possition, front view is presented right-offseted by rearViewRevealWidth+rearViewRevealOverdraw + FrontViewPositionRightMost, + + // Front controller is removed from view. Animated transitioning from this state will cause the same + // effect than animating from FrontViewPositionRightMost. Use this instead of FrontViewPositionRightMost when + // you intent to remove the front controller view from the view hierarchy. + FrontViewPositionRightMostRemoved, + +}; + +// Enum values for toggleAnimationType +typedef NS_ENUM(NSInteger, SWRevealToggleAnimationType) +{ + SWRevealToggleAnimationTypeSpring, // <- produces a spring based animation + SWRevealToggleAnimationTypeEaseOut, // <- produces an ease out curve animation +}; + + +@interface SWRevealViewController : UIViewController + +/* Basic API */ + +// Object instance init and rear view setting +- (id)initWithRearViewController:(UIViewController *)rearViewController frontViewController:(UIViewController *)frontViewController; + +// Rear view controller, can be nil if not used +@property (nonatomic) UIViewController *rearViewController; +- (void)setRearViewController:(UIViewController *)rearViewController animated:(BOOL)animated; + +// Optional right view controller, can be nil if not used +@property (nonatomic) UIViewController *rightViewController; +- (void)setRightViewController:(UIViewController *)rightViewController animated:(BOOL)animated; + +// Front view controller, can be nil on initialization but must be supplied by the time the view is loaded +@property (nonatomic) UIViewController *frontViewController; +- (void)setFrontViewController:(UIViewController *)frontViewController animated:(BOOL)animated; + +// Sets the frontViewController using a default set of chained animations consisting on moving the +// presented frontViewController to the right most possition, replacing it, and moving it back to the left position +- (void)pushFrontViewController:(UIViewController *)frontViewController animated:(BOOL)animated; + +// Sets the frontViewController position. You can call the animated version several times with different +// positions to obtain a set of animations that will be performed in order one after the other. +@property (nonatomic) FrontViewPosition frontViewPosition; +- (void)setFrontViewPosition:(FrontViewPosition)frontViewPosition animated:(BOOL)animated; + +// The following methods are meant to be directly connected to the action method of a button +// to perform user triggered postion change of the controller views. This is ussually added to a +// button on top left or right of the frontViewController +- (IBAction)revealToggle:(id)sender; +- (IBAction)rightRevealToggle:(id)sender; // <-- simetric implementation of the above for the rightViewController + +// Toogles the current state of the front controller between Left or Right and fully visible +// Use setFrontViewPosition to set a particular position +- (void)revealToggleAnimated:(BOOL)animated; +- (void)rightRevealToggleAnimated:(BOOL)animated; // <-- simetric implementation of the above for the rightViewController + +// The following method will provide a panGestureRecognizer suitable to be added to any view +// in order to perform usual drag and swipe gestures to reveal the rear views. This is usually added to the top bar +// of a front controller, but it can be added to your frontViewController view or to the reveal controller view to provide full screen panning. +// By default, the panGestureRecognizer is added to the view containing the front controller view. To keep this default behavior +// you still need to call this method, just don't add it to any of your views. The default setup allows you to dissable +// user interactions on your controller views without affecting the recognizer. +- (UIPanGestureRecognizer*)panGestureRecognizer; + +// The following method will provide a tapGestureRecognizer suitable to be added to any view on the frontController +// for concealing the rear views. By default no tap recognizer is created or added to any view, however if you call this method after +// the controller's view has been loaded the recognizer is added to the reveal controller's front container view. +// Thus, you can disable user interactions on your frontViewController view without affecting the tap recognizer. +- (UITapGestureRecognizer*)tapGestureRecognizer; + +/* The following properties are provided for further customization, they are set to default values on initialization, + you do not generally have to set them */ + +// Defines how much of the rear or right view is shown, default is 260. +// Negative values indicate that the reveal width should be computed by substracting the full front view width, +// so the revealed frontView width is kept constant when bounds change as opposed to the rear or right width. +@property (nonatomic) CGFloat rearViewRevealWidth; +@property (nonatomic) CGFloat rightViewRevealWidth; // <-- simetric implementation of the above for the rightViewController + +// Defines how much of an overdraw can occur when dragging further than 'rearViewRevealWidth', default is 60. +@property (nonatomic) CGFloat rearViewRevealOverdraw; +@property (nonatomic) CGFloat rightViewRevealOverdraw; // <-- simetric implementation of the above for the rightViewController + +// Defines how much displacement is applied to the rear view when animating or dragging the front view, default is 40. +@property (nonatomic) CGFloat rearViewRevealDisplacement; +@property (nonatomic) CGFloat rightViewRevealDisplacement; // <-- simetric implementation of the above for the rightViewController + +// Defines a width on the border of the view attached to the panGesturRecognizer where the gesture is allowed, +// default is 0 which means no restriction. +@property (nonatomic) CGFloat draggableBorderWidth; + +// If YES (the default) the controller will bounce to the Left position when dragging further than 'rearViewRevealWidth' +@property (nonatomic) BOOL bounceBackOnOverdraw; +@property (nonatomic) BOOL bounceBackOnLeftOverdraw; // <-- simetric implementation of the above for the rightViewController + +// If YES (default is NO) the controller will allow permanent dragging up to the rightMostPosition +@property (nonatomic) BOOL stableDragOnOverdraw; +@property (nonatomic) BOOL stableDragOnLeftOverdraw; // <-- simetric implementation of the above for the rightViewController + +// If YES (default is NO) the front view controller will be ofsseted vertically by the height of a navigation bar. +// Use this on iOS7 when you add an instance of RevealViewController as a child of a UINavigationController (or another SWRevealViewController) +// and you want the front view controller to be presented below the navigation bar of its UINavigationController grand parent. +// The rearViewController will still appear full size and blurred behind the navigation bar of its UINavigationController grand parent +@property (nonatomic) BOOL presentFrontViewHierarchically; + +// Velocity required for the controller to toggle its state based on a swipe movement, default is 250 +@property (nonatomic) CGFloat quickFlickVelocity; + +// Duration for the revealToggle animation, default is 0.25 +@property (nonatomic) NSTimeInterval toggleAnimationDuration; + +// Animation type, default is SWRevealToggleAnimationTypeSpring +@property (nonatomic) SWRevealToggleAnimationType toggleAnimationType; + +// When animation type is SWRevealToggleAnimationTypeSpring determines the damping ratio, default is 1 +@property (nonatomic) CGFloat springDampingRatio; + +// Duration for animated replacement of view controllers +@property (nonatomic) NSTimeInterval replaceViewAnimationDuration; + +// Defines the radius of the front view's shadow, default is 2.5f +@property (nonatomic) CGFloat frontViewShadowRadius; + +// Defines the radius of the front view's shadow offset default is {0.0f,2.5f} +@property (nonatomic) CGSize frontViewShadowOffset; + +// Defines the front view's shadow opacity, default is 1.0f +@property (nonatomic) CGFloat frontViewShadowOpacity; + +// Defines the front view's shadow color, default is blackColor +@property (nonatomic) UIColor *frontViewShadowColor; + +// Defines whether the controller should clip subviews to its view bounds. Default is NO. +// Set this to YES when you are presenting this controller as a non full-screen child of a +// custom container controller which does not explicitly clips its subviews. +@property (nonatomic) BOOL clipsViewsToBounds; + +// Defines whether your views clicable area extends beyond the bounds of this controller. Default is NO. +// Set this to YES if you are presenting this controller as a non full-screen child of a custom container and you are not +// clipping your front view to this controller bounds. +@property (nonatomic) BOOL extendsPointInsideHit; + +/* The class properly handles all the relevant calls to appearance methods on the contained controllers. + Moreover you can assign a delegate to let the class inform you on positions and animation activity */ + +// Delegate +@property (nonatomic,weak) id delegate; + +@end + + +#pragma mark - SWRevealViewControllerDelegate Protocol + +typedef enum +{ + SWRevealControllerOperationNone, + SWRevealControllerOperationReplaceRearController, + SWRevealControllerOperationReplaceFrontController, + SWRevealControllerOperationReplaceRightController, + +} SWRevealControllerOperation; + + +@protocol SWRevealViewControllerDelegate + +@optional + +// The following delegate methods will be called before and after the front view moves to a position +- (void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position; +- (void)revealController:(SWRevealViewController *)revealController didMoveToPosition:(FrontViewPosition)position; + +// This will be called inside the reveal animation, thus you can use it to place your own code that will be animated in sync +- (void)revealController:(SWRevealViewController *)revealController animateToPosition:(FrontViewPosition)position; + +// Implement this to return NO when you want the pan gesture recognizer to be ignored +- (BOOL)revealControllerPanGestureShouldBegin:(SWRevealViewController *)revealController; + +// Implement this to return NO when you want the tap gesture recognizer to be ignored +- (BOOL)revealControllerTapGestureShouldBegin:(SWRevealViewController *)revealController; + +// Implement this to return YES if you want other gesture recognizer to share touch events with the pan gesture +- (BOOL)revealController:(SWRevealViewController *)revealController + panGestureRecognizerShouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; + +// Implement this to return YES if you want other gesture recognizer to share touch events with the tap gesture +- (BOOL)revealController:(SWRevealViewController *)revealController + tapGestureRecognizerShouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; + +// Called when the gestureRecognizer began and ended +- (void)revealControllerPanGestureBegan:(SWRevealViewController *)revealController; +- (void)revealControllerPanGestureEnded:(SWRevealViewController *)revealController; + +// The following methods provide a means to track the evolution of the gesture recognizer. +// The 'location' parameter is the X origin coordinate of the front view as the user drags it +// The 'progress' parameter is a number ranging from 0 to 1 indicating the front view location relative to the +// rearRevealWidth or rightRevealWidth. 1 is fully revealed, dragging ocurring in the overDraw region will result in values above 1. +// The 'overProgress' parameter is a number ranging from 0 to 1 indicating the front view location relative to the +// overdraw region. 0 is fully revealed, 1 is fully overdrawn. Negative values occur inside the normal reveal region +- (void)revealController:(SWRevealViewController *)revealController panGestureBeganFromLocation:(CGFloat)location progress:(CGFloat)progress overProgress:(CGFloat)overProgress; +- (void)revealController:(SWRevealViewController *)revealController panGestureMovedToLocation:(CGFloat)location progress:(CGFloat)progress overProgress:(CGFloat)overProgress; +- (void)revealController:(SWRevealViewController *)revealController panGestureEndedToLocation:(CGFloat)location progress:(CGFloat)progress overProgress:(CGFloat)overProgress; + +// Notification of child controller replacement +- (void)revealController:(SWRevealViewController *)revealController willAddViewController:(UIViewController *)viewController + forOperation:(SWRevealControllerOperation)operation animated:(BOOL)animated; +- (void)revealController:(SWRevealViewController *)revealController didAddViewController:(UIViewController *)viewController + forOperation:(SWRevealControllerOperation)operation animated:(BOOL)animated; + +// Support for custom transition animations while replacing child controllers. If implemented, it will be fired in response +// to calls to 'setXXViewController' methods +- (id)revealController:(SWRevealViewController *)revealController + animationControllerForOperation:(SWRevealControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC; + +// DEPRECATED - The following delegate methods will be removed some time in the future +- (void)revealController:(SWRevealViewController *)revealController panGestureBeganFromLocation:(CGFloat)location progress:(CGFloat)progress; // (DEPRECATED) +- (void)revealController:(SWRevealViewController *)revealController panGestureMovedToLocation:(CGFloat)location progress:(CGFloat)progress; // (DEPRECATED) +- (void)revealController:(SWRevealViewController *)revealController panGestureEndedToLocation:(CGFloat)location progress:(CGFloat)progress; // (DEPRECATED) +@end + + +#pragma mark - UIViewController(SWRevealViewController) Category + +// A category of UIViewController to let childViewControllers easily access their parent SWRevealViewController +@interface UIViewController(SWRevealViewController) + +- (SWRevealViewController*)revealViewController; + +@end + + +#pragma mark - StoryBoard support Classes + +/* StoryBoard support */ + +// String identifiers to be applied to segues on a storyboard +extern NSString* const SWSegueRearIdentifier; // this is @"sw_rear" +extern NSString* const SWSegueFrontIdentifier; // this is @"sw_front" +extern NSString* const SWSegueRightIdentifier; // this is @"sw_right" + +/* This will allow the class to be defined on a storyboard */ + +// Use this along with one of the above segue identifiers to segue to the initial state +@interface SWRevealViewControllerSegueSetController : UIStoryboardSegue +@end + +// Use this to push a view controller +@interface SWRevealViewControllerSeguePushController : UIStoryboardSegue +@end + + +//#pragma mark - SWRevealViewControllerSegue (DEPRECATED) +// +//@interface SWRevealViewControllerSegue : UIStoryboardSegue // DEPRECATED: USE SWRevealViewControllerSegueSetController instead +//@property (nonatomic, strong) void(^performBlock)( SWRevealViewControllerSegue* segue, UIViewController* svc, UIViewController* dvc ); +//@end diff --git a/Limelight/ViewControllers/SWRevealViewController.m b/Limelight/ViewControllers/SWRevealViewController.m new file mode 100755 index 00000000..de73c51b --- /dev/null +++ b/Limelight/ViewControllers/SWRevealViewController.m @@ -0,0 +1,1897 @@ +/* + + Copyright (c) 2013 Joan Lluch + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is furnished + to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + Early code inspired on a similar class by Philip Kluz (Philip.Kluz@zuui.org) + +*/ + +#import + +#import "SWRevealViewController.h" + + +#pragma mark - StatusBar Helper Function + +// computes the required offset adjustment due to the status bar for the passed in view, +// it will return the statusBar height if view fully overlaps the statusBar, otherwise returns 0.0f +static CGFloat statusBarAdjustment( UIView* view ) +{ + CGFloat adjustment = 0.0f; + UIApplication *app = [UIApplication sharedApplication]; + CGRect viewFrame = [view convertRect:view.bounds toView:[app keyWindow]]; + CGRect statusBarFrame = [app statusBarFrame]; + + if ( CGRectIntersectsRect(viewFrame, statusBarFrame) ) + adjustment = fminf(statusBarFrame.size.width, statusBarFrame.size.height); + + return adjustment; +} + + +#pragma mark - SWRevealView Class + +@interface SWRevealView: UIView +{ + __weak SWRevealViewController *_c; +} + +@property (nonatomic, readonly) UIView *rearView; +@property (nonatomic, readonly) UIView *rightView; +@property (nonatomic, readonly) UIView *frontView; +@property (nonatomic, assign) BOOL disableLayout; + +@end + + +@interface SWRevealViewController() +- (void)_getRevealWidth:(CGFloat*)pRevealWidth revealOverDraw:(CGFloat*)pRevealOverdraw forSymetry:(int)symetry; +- (void)_getBounceBack:(BOOL*)pBounceBack pStableDrag:(BOOL*)pStableDrag forSymetry:(int)symetry; +- (void)_getAdjustedFrontViewPosition:(FrontViewPosition*)frontViewPosition forSymetry:(int)symetry; +@end + + +@implementation SWRevealView + + +static CGFloat scaledValue( CGFloat v1, CGFloat min2, CGFloat max2, CGFloat min1, CGFloat max1) +{ + CGFloat result = min2 + (v1-min1)*((max2-min2)/(max1-min1)); + if ( result != result ) return min2; // nan + if ( result < min2 ) return min2; + if ( result > max2 ) return max2; + return result; +} + + +- (id)initWithFrame:(CGRect)frame controller:(SWRevealViewController*)controller +{ + self = [super initWithFrame:frame]; + if ( self ) + { + _c = controller; + CGRect bounds = self.bounds; + + _frontView = [[UIView alloc] initWithFrame:bounds]; + _frontView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; + [self reloadShadow]; + + [self addSubview:_frontView]; + } + return self; +} + + +- (void)reloadShadow +{ + CALayer *frontViewLayer = _frontView.layer; + frontViewLayer.shadowColor = [_c.frontViewShadowColor CGColor]; + frontViewLayer.shadowOpacity = _c.frontViewShadowOpacity; + frontViewLayer.shadowOffset = _c.frontViewShadowOffset; + frontViewLayer.shadowRadius = _c.frontViewShadowRadius; +} + + +- (CGRect)hierarchycalFrameAdjustment:(CGRect)frame +{ + if ( _c.presentFrontViewHierarchically ) + { + UINavigationBar *dummyBar = [[UINavigationBar alloc] init]; + CGFloat barHeight = [dummyBar sizeThatFits:CGSizeMake(100,100)].height; + CGFloat offset = barHeight + statusBarAdjustment(self); + frame.origin.y += offset; + frame.size.height -= offset; + } + return frame; +} + + +- (void)prepareRearViewForPosition:(FrontViewPosition)newPosition +{ + if ( _rearView == nil ) + { + _rearView = [[UIView alloc] initWithFrame:self.bounds]; + _rearView.autoresizingMask = /*UIViewAutoresizingFlexibleWidth|*/UIViewAutoresizingFlexibleHeight; + [self insertSubview:_rearView belowSubview:_frontView]; + } + + CGFloat xLocation = [self frontLocationForPosition:_c.frontViewPosition]; + [self _layoutRearViewsForLocation:xLocation]; + [self _prepareForNewPosition:newPosition]; +} + + +- (void)prepareRightViewForPosition:(FrontViewPosition)newPosition +{ + if ( _rightView == nil ) + { + _rightView = [[UIView alloc] initWithFrame:self.bounds]; + _rightView.autoresizingMask = /*UIViewAutoresizingFlexibleWidth|*/UIViewAutoresizingFlexibleHeight; + [self insertSubview:_rightView belowSubview:_frontView]; + } + + CGFloat xLocation = [self frontLocationForPosition:_c.frontViewPosition]; + [self _layoutRearViewsForLocation:xLocation]; + [self _prepareForNewPosition:newPosition]; +} + + +- (CGFloat)frontLocationForPosition:(FrontViewPosition)frontViewPosition +{ + CGFloat revealWidth; + CGFloat revealOverdraw; + + CGFloat location = 0.0f; + + int symetry = frontViewPosition FrontViewPositionRight ) + location = revealWidth + revealOverdraw; + + return location*symetry; +} + + +- (void)dragFrontViewToXLocation:(CGFloat)xLocation +{ + CGRect bounds = self.bounds; + + xLocation = [self _adjustedDragLocationForLocation:xLocation]; + [self _layoutRearViewsForLocation:xLocation]; + + CGRect frame = CGRectMake(xLocation, 0.0f, bounds.size.width, bounds.size.height); + _frontView.frame = [self hierarchycalFrameAdjustment:frame]; +} + + +# pragma mark - overrides + +- (void)layoutSubviews +{ + if ( _disableLayout ) return; + + CGRect bounds = self.bounds; + + FrontViewPosition position = _c.frontViewPosition; + CGFloat xLocation = [self frontLocationForPosition:position]; + + // set rear view frames + [self _layoutRearViewsForLocation:xLocation]; + + // set front view frame + CGRect frame = CGRectMake(xLocation, 0.0f, bounds.size.width, bounds.size.height); + _frontView.frame = [self hierarchycalFrameAdjustment:frame]; + + // setup front view shadow path if needed (front view loaded and not removed) + UIViewController *frontViewController = _c.frontViewController; + BOOL viewLoaded = frontViewController != nil && frontViewController.isViewLoaded; + BOOL viewNotRemoved = position > FrontViewPositionLeftSideMostRemoved && position < FrontViewPositionRightMostRemoved; + CGRect shadowBounds = viewLoaded && viewNotRemoved ? _frontView.bounds : CGRectZero; + + UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:shadowBounds]; + _frontView.layer.shadowPath = shadowPath.CGPath; +} + + +- (BOOL)pointInsideD:(CGPoint)point withEvent:(UIEvent *)event +{ + BOOL isInside = [super pointInside:point withEvent:event]; + if ( _c.extendsPointInsideHit ) + { + if ( !isInside && _rearView && [_c.rearViewController isViewLoaded] ) + { + CGPoint pt = [self convertPoint:point toView:_rearView]; + isInside = [_rearView pointInside:pt withEvent:event]; + } + + if ( !isInside && _frontView && [_c.frontViewController isViewLoaded] ) + { + CGPoint pt = [self convertPoint:point toView:_frontView]; + isInside = [_frontView pointInside:pt withEvent:event]; + } + + if ( !isInside && _rightView && [_c.rightViewController isViewLoaded] ) + { + CGPoint pt = [self convertPoint:point toView:_rightView]; + isInside = [_rightView pointInside:pt withEvent:event]; + } + } + return isInside; +} + + +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event +{ + BOOL isInside = [super pointInside:point withEvent:event]; + if ( !isInside && _c.extendsPointInsideHit ) + { + UIView *testViews[] = { _rearView, _frontView, _rightView }; + UIViewController *testControllers[] = { _c.rearViewController, _c.frontViewController, _c.rightViewController }; + + for ( NSInteger i=0 ; i<3 && !isInside ; i++ ) + { + if ( testViews[i] && [testControllers[i] isViewLoaded] ) + { + CGPoint pt = [self convertPoint:point toView:testViews[i]]; + isInside = [testViews[i] pointInside:pt withEvent:event]; + } + } + } + return isInside; +} + + +# pragma mark - private + + +- (void)_layoutRearViewsForLocation:(CGFloat)xLocation +{ + CGRect bounds = self.bounds; + + CGFloat rearRevealWidth = _c.rearViewRevealWidth; + if ( rearRevealWidth < 0) rearRevealWidth = bounds.size.width + _c.rearViewRevealWidth; + + CGFloat rearXLocation = scaledValue(xLocation, -_c.rearViewRevealDisplacement, 0, 0, rearRevealWidth); + + CGFloat rearWidth = rearRevealWidth + _c.rearViewRevealOverdraw; + _rearView.frame = CGRectMake(rearXLocation, 0.0, rearWidth, bounds.size.height); + + CGFloat rightRevealWidth = _c.rightViewRevealWidth; + if ( rightRevealWidth < 0) rightRevealWidth = bounds.size.width + _c.rightViewRevealWidth; + + CGFloat rightXLocation = scaledValue(xLocation, 0, _c.rightViewRevealDisplacement, -rightRevealWidth, 0); + + CGFloat rightWidth = rightRevealWidth + _c.rightViewRevealOverdraw; + _rightView.frame = CGRectMake(bounds.size.width-rightWidth+rightXLocation, 0.0f, rightWidth, bounds.size.height); +} + + +- (void)_prepareForNewPosition:(FrontViewPosition)newPosition; +{ + if ( _rearView == nil || _rightView == nil ) + return; + + int symetry = newPosition 0 && rearIndex < rightIndex) ) + [self exchangeSubviewAtIndex:rightIndex withSubviewAtIndex:rearIndex]; +} + + +- (CGFloat)_adjustedDragLocationForLocation:(CGFloat)x +{ + CGFloat result; + + CGFloat revealWidth; + CGFloat revealOverdraw; + BOOL bounceBack; + BOOL stableDrag; + FrontViewPosition position = _c.frontViewPosition; + + int symetry = x<0 ? -1 : 1; + + [_c _getRevealWidth:&revealWidth revealOverDraw:&revealOverdraw forSymetry:symetry]; + [_c _getBounceBack:&bounceBack pStableDrag:&stableDrag forSymetry:symetry]; + + BOOL stableTrack = !bounceBack || stableDrag || position==FrontViewPositionRightMost || position==FrontViewPositionLeftSideMost; + if ( stableTrack ) + { + revealWidth += revealOverdraw; + revealOverdraw = 0.0f; + } + + x = x * symetry; + + if (x <= revealWidth) + result = x; // Translate linearly. + + else if (x <= revealWidth+2*revealOverdraw) + result = revealWidth + (x-revealWidth)/2; // slow down translation by halph the movement. + + else + result = revealWidth+revealOverdraw; // keep at the rightMost location. + + return result * symetry; +} + +@end + + +#pragma mark - SWContextTransitioningObject + +@interface SWContextTransitionObject : NSObject +@end + + +@implementation SWContextTransitionObject +{ + __weak SWRevealViewController *_revealVC; + UIView *_view; + UIViewController *_toVC; + UIViewController *_fromVC; + void (^_completion)(void); +} + +- (id)initWithRevealController:(SWRevealViewController*)revealVC containerView:(UIView*)view fromVC:(UIViewController*)fromVC + toVC:(UIViewController*)toVC completion:(void (^)(void))completion +{ + self = [super init]; + if ( self ) + { + _revealVC = revealVC; + _view = view; + _fromVC = fromVC; + _toVC = toVC; + _completion = completion; + } + return self; +} + + +- (UIView *)containerView +{ + return _view; +} + + +- (BOOL)isAnimated +{ + return YES; +} + + +- (BOOL)isInteractive +{ + return NO; // not supported +} + +- (BOOL)transitionWasCancelled +{ + return NO; // not supported +} + +- (CGAffineTransform)targetTransform { + return CGAffineTransformIdentity; +} + +- (UIModalPresentationStyle)presentationStyle +{ + return UIModalPresentationNone; // not applicable +} + + +- (void)updateInteractiveTransition:(CGFloat)percentComplete +{ + // not supported +} + + +- (void)finishInteractiveTransition +{ + // not supported +} + + +- (void)cancelInteractiveTransition +{ + // not supported +} + + +- (void)completeTransition:(BOOL)didComplete +{ + _completion(); +} + + +- (UIViewController *)viewControllerForKey:(NSString *)key +{ + if ( [key isEqualToString:UITransitionContextFromViewControllerKey] ) + return _fromVC; + + if ( [key isEqualToString:UITransitionContextToViewControllerKey] ) + return _toVC; + + return nil; +} + + +- (UIView *)viewForKey:(NSString *)key +{ + return nil; +} + + +- (CGRect)initialFrameForViewController:(UIViewController *)vc +{ + return _view.bounds; +} + + +- (CGRect)finalFrameForViewController:(UIViewController *)vc +{ + return _view.bounds; +} + +@end + + +#pragma mark - SWDefaultAnimationController Class + +@interface SWDefaultAnimationController : NSObject +@end + +@implementation SWDefaultAnimationController +{ + NSTimeInterval _duration; +} + + +- (id)initWithDuration:(NSTimeInterval)duration +{ + self = [super init]; + if ( self ) + { + _duration = duration; + } + return self; +} + + +- (NSTimeInterval)transitionDuration:(id)transitionContext +{ + return _duration; +} + + +- (void)animateTransition:(id )transitionContext +{ + UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; + UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; + + if ( fromViewController ) + { + [UIView transitionFromView:fromViewController.view toView:toViewController.view duration:_duration + options:UIViewAnimationOptionTransitionCrossDissolve|UIViewAnimationOptionOverrideInheritedOptions + completion:^(BOOL finished) { [transitionContext completeTransition:finished]; }]; + } + else + { + // tansitionFromView does not correctly handle the case where the fromView is nil (at least on iOS7) it just pops up the toView view with no animation, + // so in such case we replace the crossDissolve animation by a simple alpha animation on the appearing view + UIView *toView = toViewController.view; + CGFloat alpha = toView.alpha; + toView.alpha = 0; + + [UIView animateWithDuration:_duration delay:0 options:UIViewAnimationOptionCurveEaseOut + animations:^{ toView.alpha = alpha;} + completion:^(BOOL finished) { [transitionContext completeTransition:finished];}]; + } +} + +@end + + +#pragma mark - SWRevealViewControllerPanGestureRecognizer + +#import + +@interface SWRevealViewControllerPanGestureRecognizer : UIPanGestureRecognizer +@end + +@implementation SWRevealViewControllerPanGestureRecognizer +{ + BOOL _dragging; + CGPoint _beginPoint; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesBegan:touches withEvent:event]; + + UITouch *touch = [touches anyObject]; + _beginPoint = [touch locationInView:self.view]; + _dragging = NO; +} + + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesMoved:touches withEvent:event]; + + if ( _dragging || self.state == UIGestureRecognizerStateFailed) + return; + + const int kDirectionPanThreshold = 5; + + UITouch *touch = [touches anyObject]; + CGPoint nowPoint = [touch locationInView:self.view]; + + if (abs(nowPoint.x - _beginPoint.x) > kDirectionPanThreshold) _dragging = YES; + else if (abs(nowPoint.y - _beginPoint.y) > kDirectionPanThreshold) self.state = UIGestureRecognizerStateFailed; +} + +@end + + +#pragma mark - SWRevealViewController Class + +@interface SWRevealViewController() +{ + SWRevealView *_contentView; + UIPanGestureRecognizer *_panGestureRecognizer; + UITapGestureRecognizer *_tapGestureRecognizer; + FrontViewPosition _frontViewPosition; + FrontViewPosition _rearViewPosition; + FrontViewPosition _rightViewPosition; + SWContextTransitionObject *_rearTransitioningController; + SWContextTransitionObject *_frontTransitioningController; + SWContextTransitionObject *_rightTransitioningController; +} +@end + + +@implementation SWRevealViewController +{ + FrontViewPosition _panInitialFrontPosition; + NSMutableArray *_animationQueue; + BOOL _userInteractionStore; +} + +const int FrontViewPositionNone = 0xff; + + +#pragma mark - Init + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + if ( self ) + { + [self _initDefaultProperties]; + } + return self; +} + + +- (id)init +{ + return [self initWithRearViewController:nil frontViewController:nil]; +} + + +- (id)initWithRearViewController:(UIViewController *)rearViewController frontViewController:(UIViewController *)frontViewController; +{ + self = [super init]; + if ( self ) + { + [self _initDefaultProperties]; + [self _performTransitionOperation:SWRevealControllerOperationReplaceRearController withViewController:rearViewController animated:NO]; + [self _performTransitionOperation:SWRevealControllerOperationReplaceFrontController withViewController:frontViewController animated:NO]; + } + return self; +} + + +- (void)_initDefaultProperties +{ + _frontViewPosition = FrontViewPositionLeft; + _rearViewPosition = FrontViewPositionLeft; + _rightViewPosition = FrontViewPositionLeft; + _rearViewRevealWidth = 260.0f; + _rearViewRevealOverdraw = 60.0f; + _rearViewRevealDisplacement = 40.0f; + _rightViewRevealWidth = 260.0f; + _rightViewRevealOverdraw = 60.0f; + _rightViewRevealDisplacement = 40.0f; + _bounceBackOnOverdraw = YES; + _bounceBackOnLeftOverdraw = YES; + _stableDragOnOverdraw = NO; + _stableDragOnLeftOverdraw = NO; + _presentFrontViewHierarchically = NO; + _quickFlickVelocity = 250.0f; + _toggleAnimationDuration = 0.3; + _toggleAnimationType = SWRevealToggleAnimationTypeSpring; + _springDampingRatio = 1; + _replaceViewAnimationDuration = 0.25; + _frontViewShadowRadius = 2.5f; + _frontViewShadowOffset = CGSizeMake(0.0f, 2.5f); + _frontViewShadowOpacity = 1.0f; + _frontViewShadowColor = [UIColor blackColor]; + _userInteractionStore = YES; + _animationQueue = [NSMutableArray array]; + _draggableBorderWidth = 0.0f; + _clipsViewsToBounds = NO; + _extendsPointInsideHit = NO; +} + + +#pragma mark - StatusBar + +- (UIViewController *)childViewControllerForStatusBarStyle +{ + int positionDif = _frontViewPosition - FrontViewPositionLeft; + + UIViewController *controller = _frontViewController; + if ( positionDif > 0 ) controller = _rearViewController; + else if ( positionDif < 0 ) controller = _rightViewController; + + return controller; +} + +- (UIViewController *)childViewControllerForStatusBarHidden +{ + UIViewController *controller = [self childViewControllerForStatusBarStyle]; + return controller; +} + + +#pragma mark - View lifecycle + +- (void)loadView +{ + // Do not call super, to prevent the apis from unfruitful looking for inexistent xibs! + //[super loadView]; + + // load any defined front/rear controllers from the storyboard before + [self loadStoryboardControllers]; + + // This is what Apple used to tell us to set as the initial frame, which is of course totally irrelevant + // with view controller containment patterns, let's leave it for the sake of it! + // CGRect frame = [[UIScreen mainScreen] applicationFrame]; + + // On iOS7 the applicationFrame does not return the whole screen. This is possibly a bug. + // As a workaround we use the screen bounds, this still works on iOS6, any zero based frame would work anyway! + CGRect frame = [[UIScreen mainScreen] bounds]; + + // create a custom content view for the controller + _contentView = [[SWRevealView alloc] initWithFrame:frame controller:self]; + + // set the content view to resize along with its superview + [_contentView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight]; + + // set the content view to clip its bounds if requested + [_contentView setClipsToBounds:_clipsViewsToBounds]; + + // set our contentView to the controllers view + self.view = _contentView; + + // Apple also tells us to do this: + _contentView.backgroundColor = [UIColor blackColor]; + + // we set the current frontViewPosition to none before seting the + // desired initial position, this will force proper controller reload + FrontViewPosition initialPosition = _frontViewPosition; + _frontViewPosition = FrontViewPositionNone; + _rearViewPosition = FrontViewPositionNone; + _rightViewPosition = FrontViewPositionNone; + + // now set the desired initial position + [self _setFrontViewPosition:initialPosition withDuration:0.0]; +} + + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + + // Uncomment the following code if you want the child controllers + // to be loaded at this point. + // + // We leave this commented out because we think loading childs here is conceptually wrong. + // Instead, we refrain view loads until necesary, for example we may never load + // the rear controller view -or the front controller view- if it is never displayed. + // + // If you need to manipulate views of any of your child controllers in an override + // of this method, you can load yourself the views explicitly on your overriden method. + // However we discourage it as an app following the MVC principles should never need to do so + +// [_frontViewController view]; +// [_rearViewController view]; + + // we store at this point the view's user interaction state as we may temporarily disable it + // and resume it back to the previous state, it is possible to override this behaviour by + // intercepting it on the panGestureBegan and panGestureEnded delegates + _userInteractionStore = _contentView.userInteractionEnabled; +} + + +- (NSUInteger)supportedInterfaceOrientations +{ + // we could have simply not implemented this, but we choose to call super to make explicit that we + // want the default behavior. + return [super supportedInterfaceOrientations]; +} + + +#pragma mark - Public methods and property accessors + +- (void)setFrontViewController:(UIViewController *)frontViewController +{ + [self setFrontViewController:frontViewController animated:NO]; +} + + +- (void)setFrontViewController:(UIViewController *)frontViewController animated:(BOOL)animated +{ + if ( ![self isViewLoaded]) + { + [self _performTransitionOperation:SWRevealControllerOperationReplaceFrontController withViewController:frontViewController animated:NO]; + return; + } + + [self _dispatchTransitionOperation:SWRevealControllerOperationReplaceFrontController withViewController:frontViewController animated:animated]; +} + + +- (void)pushFrontViewController:(UIViewController *)frontViewController animated:(BOOL)animated +{ + if ( ![self isViewLoaded]) + { + [self _performTransitionOperation:SWRevealControllerOperationReplaceFrontController withViewController:frontViewController animated:NO]; + return; + } + + [self _dispatchPushFrontViewController:frontViewController animated:animated]; +} + + +- (void)setRearViewController:(UIViewController *)rearViewController +{ + [self setRearViewController:rearViewController animated:NO]; +} + + +- (void)setRearViewController:(UIViewController *)rearViewController animated:(BOOL)animated +{ + if ( ![self isViewLoaded]) + { + [self _performTransitionOperation:SWRevealControllerOperationReplaceRearController withViewController:rearViewController animated:NO]; + return; + } + + [self _dispatchTransitionOperation:SWRevealControllerOperationReplaceRearController withViewController:rearViewController animated:animated]; +} + + +- (void)setRightViewController:(UIViewController *)rightViewController +{ + [self setRightViewController:rightViewController animated:NO]; +} + + +- (void)setRightViewController:(UIViewController *)rightViewController animated:(BOOL)animated +{ + if ( ![self isViewLoaded]) + { + [self _performTransitionOperation:SWRevealControllerOperationReplaceRightController withViewController:rightViewController animated:NO]; + return; + } + + [self _dispatchTransitionOperation:SWRevealControllerOperationReplaceRightController withViewController:rightViewController animated:animated]; +} + + +- (void)revealToggleAnimated:(BOOL)animated +{ + FrontViewPosition toggledFrontViewPosition = FrontViewPositionLeft; + if (_frontViewPosition <= FrontViewPositionLeft) + toggledFrontViewPosition = FrontViewPositionRight; + + [self setFrontViewPosition:toggledFrontViewPosition animated:animated]; +} + + +- (void)rightRevealToggleAnimated:(BOOL)animated +{ + FrontViewPosition toggledFrontViewPosition = FrontViewPositionLeft; + if (_frontViewPosition >= FrontViewPositionLeft) + toggledFrontViewPosition = FrontViewPositionLeftSide; + + [self setFrontViewPosition:toggledFrontViewPosition animated:animated]; +} + + +- (void)setFrontViewPosition:(FrontViewPosition)frontViewPosition +{ + [self setFrontViewPosition:frontViewPosition animated:NO]; +} + + +- (void)setFrontViewPosition:(FrontViewPosition)frontViewPosition animated:(BOOL)animated +{ + if ( ![self isViewLoaded] ) + { + _frontViewPosition = frontViewPosition; + _rearViewPosition = frontViewPosition; + _rightViewPosition = frontViewPosition; + return; + } + + [self _dispatchSetFrontViewPosition:frontViewPosition animated:animated]; +} + + +- (void)setFrontViewShadowRadius:(CGFloat)frontViewShadowRadius +{ + _frontViewShadowRadius = frontViewShadowRadius; + [_contentView reloadShadow]; +} + + +- (void)setFrontViewShadowOffset:(CGSize)frontViewShadowOffset +{ + _frontViewShadowOffset = frontViewShadowOffset; + [_contentView reloadShadow]; +} + + +- (void)setFrontViewShadowOpacity:(CGFloat)frontViewShadowOpacity +{ + _frontViewShadowOpacity = frontViewShadowOpacity; + [_contentView reloadShadow]; +} + + +- (void)setFrontViewShadowColor:(UIColor *)frontViewShadowColor +{ + _frontViewShadowColor = frontViewShadowColor; + [_contentView reloadShadow]; +} + + +- (UIPanGestureRecognizer*)panGestureRecognizer +{ + if ( _panGestureRecognizer == nil ) + { + _panGestureRecognizer = [[SWRevealViewControllerPanGestureRecognizer alloc] initWithTarget:self action:@selector(_handleRevealGesture:)]; + _panGestureRecognizer.delegate = self; + [_contentView.frontView addGestureRecognizer:_panGestureRecognizer]; + } + return _panGestureRecognizer; +} + + +- (UITapGestureRecognizer*)tapGestureRecognizer +{ + if ( _tapGestureRecognizer == nil ) + { + UITapGestureRecognizer *tapRecognizer = + [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_handleTapGesture:)]; + + tapRecognizer.delegate = self; + [_contentView.frontView addGestureRecognizer:tapRecognizer]; + _tapGestureRecognizer = tapRecognizer ; + } + return _tapGestureRecognizer; +} + + +- (void)setClipsViewsToBounds:(BOOL)clipsViewsToBounds +{ + _clipsViewsToBounds = clipsViewsToBounds; + [_contentView setClipsToBounds:clipsViewsToBounds]; +} + + + +#pragma mark - Provided acction methods + +- (IBAction)revealToggle:(id)sender +{ + [self revealToggleAnimated:YES]; +} + + +- (IBAction)rightRevealToggle:(id)sender +{ + [self rightRevealToggleAnimated:YES]; +} + + +#pragma mark - UserInteractionEnabling + +// disable userInteraction on the entire control +- (void)_disableUserInteraction +{ + [_contentView setUserInteractionEnabled:NO]; + [_contentView setDisableLayout:YES]; +} + +// restore userInteraction on the control +- (void)_restoreUserInteraction +{ + // we use the stored userInteraction state just in case a developer decided + // to have our view interaction disabled beforehand + [_contentView setUserInteractionEnabled:_userInteractionStore]; + [_contentView setDisableLayout:NO]; +} + + +#pragma mark - PanGesture progress notification + +- (void)_notifyPanGestureBegan +{ + if ( [_delegate respondsToSelector:@selector(revealControllerPanGestureBegan:)] ) + [_delegate revealControllerPanGestureBegan:self]; + + CGFloat xLocation, dragProgress, overProgress; + [self _getDragLocation:&xLocation progress:&dragProgress overdrawProgress:&overProgress]; + + if ( [_delegate respondsToSelector:@selector(revealController:panGestureBeganFromLocation:progress:overProgress:)] ) + [_delegate revealController:self panGestureBeganFromLocation:xLocation progress:dragProgress overProgress:overProgress]; + + else if ( [_delegate respondsToSelector:@selector(revealController:panGestureBeganFromLocation:progress:)] ) + [_delegate revealController:self panGestureBeganFromLocation:xLocation progress:dragProgress]; +} + +- (void)_notifyPanGestureMoved +{ + CGFloat xLocation, dragProgress, overProgress; + [self _getDragLocation:&xLocation progress:&dragProgress overdrawProgress:&overProgress]; + + if ( [_delegate respondsToSelector:@selector(revealController:panGestureMovedToLocation:progress:overProgress:)] ) + [_delegate revealController:self panGestureMovedToLocation:xLocation progress:dragProgress overProgress:overProgress]; + + else if ( [_delegate respondsToSelector:@selector(revealController:panGestureMovedToLocation:progress:)] ) + [_delegate revealController:self panGestureMovedToLocation:xLocation progress:dragProgress]; +} + +- (void)_notifyPanGestureEnded +{ + CGFloat xLocation, dragProgress, overProgress; + [self _getDragLocation:&xLocation progress:&dragProgress overdrawProgress:&overProgress]; + + if ( [_delegate respondsToSelector:@selector(revealController:panGestureEndedToLocation:progress:overProgress:)] ) + [_delegate revealController:self panGestureEndedToLocation:xLocation progress:dragProgress overProgress:overProgress]; + + else if ( [_delegate respondsToSelector:@selector(revealController:panGestureEndedToLocation:progress:)] ) + [_delegate revealController:self panGestureEndedToLocation:xLocation progress:dragProgress]; + + if ( [_delegate respondsToSelector:@selector(revealControllerPanGestureEnded:)] ) + [_delegate revealControllerPanGestureEnded:self]; +} + + +#pragma mark - Symetry + +- (void)_getRevealWidth:(CGFloat*)pRevealWidth revealOverDraw:(CGFloat*)pRevealOverdraw forSymetry:(int)symetry +{ + if ( symetry < 0 ) *pRevealWidth = _rightViewRevealWidth, *pRevealOverdraw = _rightViewRevealOverdraw; + else *pRevealWidth = _rearViewRevealWidth, *pRevealOverdraw = _rearViewRevealOverdraw; + + if (*pRevealWidth < 0) *pRevealWidth = _contentView.bounds.size.width + *pRevealWidth; +} + +- (void)_getBounceBack:(BOOL*)pBounceBack pStableDrag:(BOOL*)pStableDrag forSymetry:(int)symetry +{ + if ( symetry < 0 ) *pBounceBack = _bounceBackOnLeftOverdraw, *pStableDrag = _stableDragOnLeftOverdraw; + else *pBounceBack = _bounceBackOnOverdraw, *pStableDrag = _stableDragOnOverdraw; +} + +- (void)_getAdjustedFrontViewPosition:(FrontViewPosition*)frontViewPosition forSymetry:(int)symetry +{ + if ( symetry < 0 ) *frontViewPosition = FrontViewPositionLeft + symetry*(*frontViewPosition-FrontViewPositionLeft); +} + +- (void)_getDragLocationx:(CGFloat*)xLocation progress:(CGFloat*)progress +{ + UIView *frontView = _contentView.frontView; + *xLocation = frontView.frame.origin.x; + + int symetry = *xLocation<0 ? -1 : 1; + + CGFloat xWidth = symetry < 0 ? _rightViewRevealWidth : _rearViewRevealWidth; + if ( xWidth < 0 ) xWidth = _contentView.bounds.size.width + xWidth; + + *progress = *xLocation/xWidth * symetry; +} + +- (void)_getDragLocation:(CGFloat*)xLocation progress:(CGFloat*)progress overdrawProgress:(CGFloat*)overProgress +{ + UIView *frontView = _contentView.frontView; + *xLocation = frontView.frame.origin.x; + + int symetry = *xLocation<0 ? -1 : 1; + + CGFloat xWidth = symetry < 0 ? _rightViewRevealWidth : _rearViewRevealWidth; + CGFloat xOverWidth = symetry < 0 ? _rightViewRevealOverdraw : _rearViewRevealOverdraw; + + if ( xWidth < 0 ) xWidth = _contentView.bounds.size.width + xWidth; + + *progress = *xLocation*symetry/xWidth; + *overProgress = (*xLocation*symetry-xWidth)/xOverWidth; +} + + +#pragma mark - Deferred block execution queue + +// Define a convenience macro to enqueue single statements +#define _enqueue(code) [self _enqueueBlock:^{code;}]; + +// Defers the execution of the passed in block until a paired _dequeue call is received, +// or executes the block right away if no pending requests are present. +- (void)_enqueueBlock:(void (^)(void))block +{ + [_animationQueue insertObject:block atIndex:0]; + if ( _animationQueue.count == 1) + { + block(); + } +} + +// Removes the top most block in the queue and executes the following one if any. +// Calls to this method must be paired with calls to _enqueueBlock, particularly it may be called +// from within a block passed to _enqueueBlock to remove itself when done with animations. +- (void)_dequeue +{ + [_animationQueue removeLastObject]; + + if ( _animationQueue.count > 0 ) + { + void (^block)(void) = [_animationQueue lastObject]; + block(); + } +} + + +#pragma mark - Gesture Delegate + +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)recognizer +{ + // only allow gesture if no previous request is in process + if ( _animationQueue.count == 0 ) + { + if ( recognizer == _panGestureRecognizer ) + return [self _panGestureShouldBegin]; + + if ( recognizer == _tapGestureRecognizer ) + return [self _tapGestureShouldBegin]; + } + + return NO; +} + + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer +{ + if ( gestureRecognizer == _panGestureRecognizer ) + { + if ( [_delegate respondsToSelector:@selector(revealController:panGestureRecognizerShouldRecognizeSimultaneouslyWithGestureRecognizer:)] ) + if ( [_delegate revealController:self panGestureRecognizerShouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer] != NO ) + return YES; + } + if ( gestureRecognizer == _tapGestureRecognizer ) + { + if ( [_delegate respondsToSelector:@selector(revealController:tapGestureRecognizerShouldRecognizeSimultaneouslyWithGestureRecognizer:)] ) + if ( [_delegate revealController:self tapGestureRecognizerShouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer] != NO ) + return YES; + } + + return NO; +} + + +- (BOOL)_tapGestureShouldBegin +{ + if ( _frontViewPosition == FrontViewPositionLeft || + _frontViewPosition == FrontViewPositionRightMostRemoved || + _frontViewPosition == FrontViewPositionLeftSideMostRemoved ) + return NO; + + // forbid gesture if the following delegate is implemented and returns NO + if ( [_delegate respondsToSelector:@selector(revealControllerTapGestureShouldBegin:)] ) + if ( [_delegate revealControllerTapGestureShouldBegin:self] == NO ) + return NO; + + return YES; +} + + +- (BOOL)_panGestureShouldBegin +{ + // forbid gesture if the initial translation is not horizontal + UIView *recognizerView = _panGestureRecognizer.view; + CGPoint translation = [_panGestureRecognizer translationInView:recognizerView]; +// NSLog( @"translation:%@", NSStringFromCGPoint(translation) ); +// if ( fabs(translation.y/translation.x) > 1 ) +// return NO; + + // forbid gesture if the following delegate is implemented and returns NO + if ( [_delegate respondsToSelector:@selector(revealControllerPanGestureShouldBegin:)] ) + if ( [_delegate revealControllerPanGestureShouldBegin:self] == NO ) + return NO; + + CGFloat xLocation = [_panGestureRecognizer locationInView:recognizerView].x; + CGFloat width = recognizerView.bounds.size.width; + + BOOL draggableBorderAllowing = ( + /*_frontViewPosition != FrontViewPositionLeft ||*/ _draggableBorderWidth == 0.0f || + (_rearViewController && xLocation <= _draggableBorderWidth) || + (_rightViewController && xLocation >= (width - _draggableBorderWidth)) ); + + + BOOL translationForbidding = ( _frontViewPosition == FrontViewPositionLeft && + ((_rearViewController == nil && translation.x > 0) || (_rightViewController == nil && translation.x < 0)) ); + + // allow gesture only within the bounds defined by the draggableBorderWidth property + return draggableBorderAllowing && !translationForbidding ; +} + + +#pragma mark - Gesture Based Reveal + +- (void)_handleTapGesture:(UITapGestureRecognizer *)recognizer +{ + NSTimeInterval duration = _toggleAnimationDuration; + [self _setFrontViewPosition:FrontViewPositionLeft withDuration:duration]; +} + + +- (void)_handleRevealGesture:(UIPanGestureRecognizer *)recognizer +{ + switch ( recognizer.state ) + { + case UIGestureRecognizerStateBegan: + [self _handleRevealGestureStateBeganWithRecognizer:recognizer]; + break; + + case UIGestureRecognizerStateChanged: + [self _handleRevealGestureStateChangedWithRecognizer:recognizer]; + break; + + case UIGestureRecognizerStateEnded: + [self _handleRevealGestureStateEndedWithRecognizer:recognizer]; + break; + + case UIGestureRecognizerStateCancelled: + //case UIGestureRecognizerStateFailed: + [self _handleRevealGestureStateCancelledWithRecognizer:recognizer]; + break; + + default: + break; + } +} + + +- (void)_handleRevealGestureStateBeganWithRecognizer:(UIPanGestureRecognizer *)recognizer +{ + // we know that we will not get here unless the animationQueue is empty because the recognizer + // delegate prevents it, however we do not want any forthcoming programatic actions to disturb + // the gesture, so we just enqueue a dummy block to ensure any programatic acctions will be + // scheduled after the gesture is completed + [self _enqueueBlock:^{}]; // <-- dummy block + + // we store the initial position and initialize a target position + _panInitialFrontPosition = _frontViewPosition; + + // we disable user interactions on the views, however programatic accions will still be + // enqueued to be performed after the gesture completes + [self _disableUserInteraction]; + [self _notifyPanGestureBegan]; +} + + +- (void)_handleRevealGestureStateChangedWithRecognizer:(UIPanGestureRecognizer *)recognizer +{ + CGFloat translation = [recognizer translationInView:_contentView].x; + + CGFloat baseLocation = [_contentView frontLocationForPosition:_panInitialFrontPosition]; + CGFloat xLocation = baseLocation + translation; + + if ( xLocation < 0 ) + { + if ( _rightViewController == nil ) xLocation = 0; + [self _rightViewDeploymentForNewFrontViewPosition:FrontViewPositionLeftSide](); + [self _rearViewDeploymentForNewFrontViewPosition:FrontViewPositionLeftSide](); + } + + if ( xLocation > 0 ) + { + if ( _rearViewController == nil ) xLocation = 0; + [self _rightViewDeploymentForNewFrontViewPosition:FrontViewPositionRight](); + [self _rearViewDeploymentForNewFrontViewPosition:FrontViewPositionRight](); + } + + [_contentView dragFrontViewToXLocation:xLocation]; + [self _notifyPanGestureMoved]; +} + + +- (void)_handleRevealGestureStateEndedWithRecognizer:(UIPanGestureRecognizer *)recognizer +{ + UIView *frontView = _contentView.frontView; + + CGFloat xLocation = frontView.frame.origin.x; + CGFloat velocity = [recognizer velocityInView:_contentView].x; + //NSLog( @"Velocity:%1.4f", velocity); + + // depending on position we compute a simetric replacement of widths and positions + int symetry = xLocation<0 ? -1 : 1; + + // simetring computing of widths + CGFloat revealWidth ; + CGFloat revealOverdraw ; + BOOL bounceBack; + BOOL stableDrag; + + [self _getRevealWidth:&revealWidth revealOverDraw:&revealOverdraw forSymetry:symetry]; + [self _getBounceBack:&bounceBack pStableDrag:&stableDrag forSymetry:symetry]; + + // simetric replacement of position + xLocation = xLocation * symetry; + + // initially we assume drag to left and default duration + FrontViewPosition frontViewPosition = FrontViewPositionLeft; + NSTimeInterval duration = _toggleAnimationDuration; + + // Velocity driven change: + if (fabsf(velocity) > _quickFlickVelocity) + { + // we may need to set the drag position and to adjust the animation duration + CGFloat journey = xLocation; + if (velocity*symetry > 0.0f) + { + frontViewPosition = FrontViewPositionRight; + journey = revealWidth - xLocation; + if (xLocation > revealWidth) + { + if (!bounceBack && stableDrag /*&& xPosition > _rearViewRevealWidth+_rearViewRevealOverdraw*0.5f*/) + { + frontViewPosition = FrontViewPositionRightMost; + journey = revealWidth+revealOverdraw - xLocation; + } + } + } + + duration = fabsf(journey/velocity); + } + + // Position driven change: + else + { + // we may need to set the drag position + if (xLocation > revealWidth*0.5f) + { + frontViewPosition = FrontViewPositionRight; + if (xLocation > revealWidth) + { + if (bounceBack) + frontViewPosition = FrontViewPositionLeft; + + else if (stableDrag && xLocation > revealWidth+revealOverdraw*0.5f) + frontViewPosition = FrontViewPositionRightMost; + } + } + } + + // symetric replacement of frontViewPosition + [self _getAdjustedFrontViewPosition:&frontViewPosition forSymetry:symetry]; + + // restore user interaction and animate to the final position + [self _restoreUserInteraction]; + [self _notifyPanGestureEnded]; + [self _setFrontViewPosition:frontViewPosition withDuration:duration]; +} + + +- (void)_handleRevealGestureStateCancelledWithRecognizer:(UIPanGestureRecognizer *)recognizer +{ + [self _restoreUserInteraction]; + [self _notifyPanGestureEnded]; + [self _dequeue]; +} + + +#pragma mark Enqueued position and controller setup + +- (void)_dispatchSetFrontViewPosition:(FrontViewPosition)frontViewPosition animated:(BOOL)animated +{ + NSTimeInterval duration = animated?_toggleAnimationDuration:0.0; + __weak SWRevealViewController *theSelf = self; + _enqueue( [theSelf _setFrontViewPosition:frontViewPosition withDuration:duration] ); +} + + +- (void)_dispatchPushFrontViewController:(UIViewController *)newFrontViewController animated:(BOOL)animated +{ + FrontViewPosition preReplacementPosition = FrontViewPositionLeft; + if ( _frontViewPosition > FrontViewPositionLeft ) preReplacementPosition = FrontViewPositionRightMost; + if ( _frontViewPosition < FrontViewPositionLeft ) preReplacementPosition = FrontViewPositionLeftSideMost; + + NSTimeInterval duration = animated?_toggleAnimationDuration:0.0; + NSTimeInterval firstDuration = duration; + int initialPosDif = abs( _frontViewPosition - preReplacementPosition ); + if ( initialPosDif == 1 ) firstDuration *= 0.8; + else if ( initialPosDif == 0 ) firstDuration = 0; + + __weak SWRevealViewController *theSelf = self; + if ( animated ) + { + _enqueue( [theSelf _setFrontViewPosition:preReplacementPosition withDuration:firstDuration] ); + _enqueue( [theSelf _performTransitionOperation:SWRevealControllerOperationReplaceFrontController withViewController:newFrontViewController animated:NO] ); + _enqueue( [theSelf _setFrontViewPosition:FrontViewPositionLeft withDuration:duration] ); + } + else + { + _enqueue( [theSelf _performTransitionOperation:SWRevealControllerOperationReplaceFrontController withViewController:newFrontViewController animated:NO] ); + } +} + + +- (void)_dispatchTransitionOperation:(SWRevealControllerOperation)operation withViewController:(UIViewController *)newViewController animated:(BOOL)animated +{ + __weak SWRevealViewController *theSelf = self; + _enqueue( [theSelf _performTransitionOperation:operation withViewController:newViewController animated:animated] ); +} + + +#pragma mark Animated view controller deployment and layout + +// Primitive method for view controller deployment and animated layout to the given position. +- (void)_setFrontViewPosition:(FrontViewPosition)newPosition withDuration:(NSTimeInterval)duration +{ + void (^rearDeploymentCompletion)() = [self _rearViewDeploymentForNewFrontViewPosition:newPosition]; + void (^rightDeploymentCompletion)() = [self _rightViewDeploymentForNewFrontViewPosition:newPosition]; + void (^frontDeploymentCompletion)() = [self _frontViewDeploymentForNewFrontViewPosition:newPosition]; + + void (^animations)() = ^() + { + // Calling this in the animation block causes the status bar to appear/dissapear in sync with our own animation + [self setNeedsStatusBarAppearanceUpdate]; + + // We call the layoutSubviews method on the contentView view and send a delegate, which will + // occur inside of an animation block if any animated transition is being performed + [_contentView layoutSubviews]; + + if ([_delegate respondsToSelector:@selector(revealController:animateToPosition:)]) + [_delegate revealController:self animateToPosition:_frontViewPosition]; + }; + + void (^completion)(BOOL) = ^(BOOL finished) + { + rearDeploymentCompletion(); + rightDeploymentCompletion(); + frontDeploymentCompletion(); + [self _dequeue]; + }; + + if ( duration > 0.0 ) + { + if ( _toggleAnimationType == SWRevealToggleAnimationTypeEaseOut ) + { + [UIView animateWithDuration:duration delay:0.0 + options:UIViewAnimationOptionCurveEaseOut animations:animations completion:completion]; + } + else + { + [UIView animateWithDuration:_toggleAnimationDuration delay:0.0 usingSpringWithDamping:_springDampingRatio initialSpringVelocity:1/duration + options:0 animations:animations completion:completion]; + } + } + else + { + animations(); + completion(YES); + } +} + + +// Primitive method for animated controller transition +//- (void)_performTransitionToViewController:(UIViewController*)new operation:(SWRevealControllerOperation)operation animated:(BOOL)animated +- (void)_performTransitionOperation:(SWRevealControllerOperation)operation withViewController:(UIViewController*)new animated:(BOOL)animated +{ + if ( [_delegate respondsToSelector:@selector(revealController:willAddViewController:forOperation:animated:)] ) + [_delegate revealController:self willAddViewController:new forOperation:operation animated:animated]; + + UIViewController *old = nil; + UIView *view = nil; + + if ( operation == SWRevealControllerOperationReplaceRearController ) + old = _rearViewController, _rearViewController = new, view = _contentView.rearView; + + else if ( operation == SWRevealControllerOperationReplaceFrontController ) + old = _frontViewController, _frontViewController = new, view = _contentView.frontView; + + else if ( operation == SWRevealControllerOperationReplaceRightController ) + old = _rightViewController, _rightViewController = new, view = _contentView.rightView; + + void (^completion)() = [self _transitionFromViewController:old toViewController:new inView:view]; + + void (^animationCompletion)() = ^ + { + completion(); + if ( [_delegate respondsToSelector:@selector(revealController:didAddViewController:forOperation:animated:)] ) + [_delegate revealController:self didAddViewController:new forOperation:operation animated:animated]; + + [self _dequeue]; + }; + + if ( animated ) + { + id animationController = nil; + + if ( [_delegate respondsToSelector:@selector(revealController:animationControllerForOperation:fromViewController:toViewController:)] ) + animationController = [_delegate revealController:self animationControllerForOperation:operation fromViewController:old toViewController:new]; + + if ( !animationController ) + animationController = [[SWDefaultAnimationController alloc] initWithDuration:_replaceViewAnimationDuration]; + + SWContextTransitionObject *transitioningObject = [[SWContextTransitionObject alloc] initWithRevealController:self containerView:view + fromVC:old toVC:new completion:animationCompletion]; + + if ( [animationController transitionDuration:transitioningObject] > 0 ) + [animationController animateTransition:transitioningObject]; + else + animationCompletion(); + } + else + { + animationCompletion(); + } +} + + +#pragma mark Position based view controller deployment + +// Deploy/Undeploy of the front view controller following the containment principles. Returns a block +// that must be invoked on animation completion in order to finish deployment +- (void (^)(void))_frontViewDeploymentForNewFrontViewPosition:(FrontViewPosition)newPosition +{ + if ( (_rightViewController == nil && newPosition < FrontViewPositionLeft) || + (_rearViewController == nil && newPosition > FrontViewPositionLeft) ) + newPosition = FrontViewPositionLeft; + + BOOL positionIsChanging = (_frontViewPosition != newPosition); + + BOOL appear = + (_frontViewPosition >= FrontViewPositionRightMostRemoved || _frontViewPosition <= FrontViewPositionLeftSideMostRemoved || _frontViewPosition == FrontViewPositionNone) && + (newPosition < FrontViewPositionRightMostRemoved && newPosition > FrontViewPositionLeftSideMostRemoved); + + BOOL disappear = + (newPosition >= FrontViewPositionRightMostRemoved || newPosition <= FrontViewPositionLeftSideMostRemoved ) && + (_frontViewPosition < FrontViewPositionRightMostRemoved && _frontViewPosition > FrontViewPositionLeftSideMostRemoved && _frontViewPosition != FrontViewPositionNone); + + if ( positionIsChanging ) + { + if ( [_delegate respondsToSelector:@selector(revealController:willMoveToPosition:)] ) + [_delegate revealController:self willMoveToPosition:newPosition]; + } + + _frontViewPosition = newPosition; + + void (^deploymentCompletion)() = + [self _deploymentForViewController:_frontViewController inView:_contentView.frontView appear:appear disappear:disappear]; + + void (^completion)() = ^() + { + deploymentCompletion(); + if ( positionIsChanging ) + { + if ( [_delegate respondsToSelector:@selector(revealController:didMoveToPosition:)] ) + [_delegate revealController:self didMoveToPosition:newPosition]; + } + }; + + return completion; +} + +// Deploy/Undeploy of the left view controller following the containment principles. Returns a block +// that must be invoked on animation completion in order to finish deployment +- (void (^)(void))_rearViewDeploymentForNewFrontViewPosition:(FrontViewPosition)newPosition +{ + if ( _presentFrontViewHierarchically ) + newPosition = FrontViewPositionRight; + + if ( _rearViewController == nil && newPosition > FrontViewPositionLeft ) + newPosition = FrontViewPositionLeft; + + BOOL appear = (_rearViewPosition <= FrontViewPositionLeft || _rearViewPosition == FrontViewPositionNone) && newPosition > FrontViewPositionLeft; + BOOL disappear = newPosition <= FrontViewPositionLeft && (_rearViewPosition > FrontViewPositionLeft && _rearViewPosition != FrontViewPositionNone); + + if ( appear ) + [_contentView prepareRearViewForPosition:newPosition]; + + _rearViewPosition = newPosition; + + return [self _deploymentForViewController:_rearViewController inView:_contentView.rearView appear:appear disappear:disappear]; +} + +// Deploy/Undeploy of the right view controller following the containment principles. Returns a block +// that must be invoked on animation completion in order to finish deployment +- (void (^)(void))_rightViewDeploymentForNewFrontViewPosition:(FrontViewPosition)newPosition +{ + if ( _rightViewController == nil && newPosition < FrontViewPositionLeft ) + newPosition = FrontViewPositionLeft; + + BOOL appear = (_rightViewPosition >= FrontViewPositionLeft || _rightViewPosition == FrontViewPositionNone) && newPosition < FrontViewPositionLeft ; + BOOL disappear = newPosition >= FrontViewPositionLeft && (_rightViewPosition < FrontViewPositionLeft && _rightViewPosition != FrontViewPositionNone); + + if ( appear ) + [_contentView prepareRightViewForPosition:newPosition]; + + _rightViewPosition = newPosition; + + return [self _deploymentForViewController:_rightViewController inView:_contentView.rightView appear:appear disappear:disappear]; +} + + +- (void (^)(void)) _deploymentForViewController:(UIViewController*)controller inView:(UIView*)view appear:(BOOL)appear disappear:(BOOL)disappear +{ + if ( appear ) return [self _deployForViewController:controller inView:view]; + if ( disappear ) return [self _undeployForViewController:controller]; + return ^{}; +} + + +#pragma mark Containment view controller deployment and transition + +// Containment Deploy method. Returns a block to be invoked at the +// animation completion, or right after return in case of non-animated deployment. +- (void (^)(void))_deployForViewController:(UIViewController*)controller inView:(UIView*)view +{ + if ( !controller || !view ) + return ^(void){}; + + CGRect frame = view.bounds; + + UIView *controllerView = controller.view; + controllerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + controllerView.frame = frame; + + if ( [controllerView isKindOfClass:[UIScrollView class]] ) + { + BOOL adjust = controller.automaticallyAdjustsScrollViewInsets; + + if ( adjust ) + { + [(id)controllerView setContentInset:UIEdgeInsetsMake(statusBarAdjustment(_contentView), 0, 0, 0)]; + } + } + + [view addSubview:controllerView]; + + void (^completionBlock)(void) = ^(void) + { + // nothing to do on completion at this stage + }; + + return completionBlock; +} + +// Containment Undeploy method. Returns a block to be invoked at the +// animation completion, or right after return in case of non-animated deployment. +- (void (^)(void))_undeployForViewController:(UIViewController*)controller +{ + if (!controller) + return ^(void){}; + + // nothing to do before completion at this stage + + void (^completionBlock)(void) = ^(void) + { + [controller.view removeFromSuperview]; + }; + + return completionBlock; +} + +// Containment Transition method. Returns a block to be invoked at the +// animation completion, or right after return in case of non-animated transition. +- (void(^)(void))_transitionFromViewController:(UIViewController*)fromController toViewController:(UIViewController*)toController inView:(UIView*)view +{ + if ( fromController == toController ) + return ^(void){}; + + if ( toController ) [self addChildViewController:toController]; + + void (^deployCompletion)() = [self _deployForViewController:toController inView:view]; + + [fromController willMoveToParentViewController:nil]; + + void (^undeployCompletion)() = [self _undeployForViewController:fromController]; + + void (^completionBlock)(void) = ^(void) + { + undeployCompletion() ; + [fromController removeFromParentViewController]; + + deployCompletion() ; + [toController didMoveToParentViewController:self]; + }; + return completionBlock; +} + +// Load any defined front/rear controllers from the storyboard +// This method is intended to be overrided in case the default behavior will not meet your needs +- (void)loadStoryboardControllers +{ + if ( self.storyboard && _rearViewController == nil ) + { + //Try each segue separately so it doesn't break prematurely if either Rear or Right views are not used. + @try + { + [self performSegueWithIdentifier:SWSegueRearIdentifier sender:nil]; + } + @catch(NSException *exception) {} + + @try + { + [self performSegueWithIdentifier:SWSegueFrontIdentifier sender:nil]; + } + @catch(NSException *exception) {} + + @try + { + [self performSegueWithIdentifier:SWSegueRightIdentifier sender:nil]; + } + @catch(NSException *exception) {} + } +} + + +#pragma mark state preservation / restoration + ++ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder*)coder +{ + SWRevealViewController* vc = nil; + UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey]; + + if (sb) + { + vc = (SWRevealViewController*)[sb instantiateViewControllerWithIdentifier:@"SWRevealViewController"]; + vc.restorationIdentifier = [identifierComponents lastObject]; + vc.restorationClass = [SWRevealViewController class]; + } + return vc; +} + + +- (void)encodeRestorableStateWithCoder:(NSCoder *)coder +{ + [coder encodeDouble:_rearViewRevealWidth forKey:@"_rearViewRevealWidth"]; + [coder encodeDouble:_rearViewRevealOverdraw forKey:@"_rearViewRevealOverdraw"]; + [coder encodeDouble:_rearViewRevealDisplacement forKey:@"_rearViewRevealDisplacement"]; + [coder encodeDouble:_rightViewRevealWidth forKey:@"_rightViewRevealWidth"]; + [coder encodeDouble:_rightViewRevealOverdraw forKey:@"_rightViewRevealOverdraw"]; + [coder encodeDouble:_rightViewRevealDisplacement forKey:@"_rightViewRevealDisplacement"]; + [coder encodeBool:_bounceBackOnOverdraw forKey:@"_bounceBackOnOverdraw"]; + [coder encodeBool:_bounceBackOnLeftOverdraw forKey:@"_bounceBackOnLeftOverdraw"]; + [coder encodeBool:_stableDragOnOverdraw forKey:@"_stableDragOnOverdraw"]; + [coder encodeBool:_stableDragOnLeftOverdraw forKey:@"_stableDragOnLeftOverdraw"]; + [coder encodeBool:_presentFrontViewHierarchically forKey:@"_presentFrontViewHierarchically"]; + [coder encodeDouble:_quickFlickVelocity forKey:@"_quickFlickVelocity"]; + [coder encodeDouble:_toggleAnimationDuration forKey:@"_toggleAnimationDuration"]; + [coder encodeInteger:_toggleAnimationType forKey:@"_toggleAnimationType"]; + [coder encodeDouble:_springDampingRatio forKey:@"_springDampingRatio"]; + [coder encodeDouble:_replaceViewAnimationDuration forKey:@"_replaceViewAnimationDuration"]; + [coder encodeDouble:_frontViewShadowRadius forKey:@"_frontViewShadowRadius"]; + [coder encodeCGSize:_frontViewShadowOffset forKey:@"_frontViewShadowOffset"]; + [coder encodeDouble:_frontViewShadowOpacity forKey:@"_frontViewShadowOpacity"]; + [coder encodeObject:_frontViewShadowColor forKey:@"_frontViewShadowColor"]; + [coder encodeBool:_userInteractionStore forKey:@"_userInteractionStore"]; + [coder encodeDouble:_draggableBorderWidth forKey:@"_draggableBorderWidth"]; + [coder encodeBool:_clipsViewsToBounds forKey:@"_clipsViewsToBounds"]; + [coder encodeBool:_extendsPointInsideHit forKey:@"_extendsPointInsideHit"]; + + [coder encodeObject:_rearViewController forKey:@"_rearViewController"]; + [coder encodeObject:_frontViewController forKey:@"_frontViewController"]; + [coder encodeObject:_rightViewController forKey:@"_rightViewController"]; + + [coder encodeInteger:_frontViewPosition forKey:@"_frontViewPosition"]; + + [super encodeRestorableStateWithCoder:coder]; +} + + +- (void)decodeRestorableStateWithCoder:(NSCoder *)coder +{ + _rearViewRevealWidth = [coder decodeDoubleForKey:@"_rearViewRevealWidth"]; + _rearViewRevealOverdraw = [coder decodeDoubleForKey:@"_rearViewRevealOverdraw"]; + _rearViewRevealDisplacement = [coder decodeDoubleForKey:@"_rearViewRevealDisplacement"]; + _rightViewRevealWidth = [coder decodeDoubleForKey:@"_rightViewRevealWidth"]; + _rightViewRevealOverdraw = [coder decodeDoubleForKey:@"_rightViewRevealOverdraw"]; + _rightViewRevealDisplacement = [coder decodeDoubleForKey:@"_rightViewRevealDisplacement"]; + _bounceBackOnOverdraw = [coder decodeBoolForKey:@"_bounceBackOnOverdraw"]; + _bounceBackOnLeftOverdraw = [coder decodeBoolForKey:@"_bounceBackOnLeftOverdraw"]; + _stableDragOnOverdraw = [coder decodeBoolForKey:@"_stableDragOnOverdraw"]; + _stableDragOnLeftOverdraw = [coder decodeBoolForKey:@"_stableDragOnLeftOverdraw"]; + _presentFrontViewHierarchically = [coder decodeBoolForKey:@"_presentFrontViewHierarchically"]; + _quickFlickVelocity = [coder decodeDoubleForKey:@"_quickFlickVelocity"]; + _toggleAnimationDuration = [coder decodeDoubleForKey:@"_toggleAnimationDuration"]; + _toggleAnimationType = [coder decodeIntegerForKey:@"_toggleAnimationType"]; + _springDampingRatio = [coder decodeDoubleForKey:@"_springDampingRatio"]; + _replaceViewAnimationDuration = [coder decodeDoubleForKey:@"_replaceViewAnimationDuration"]; + _frontViewShadowRadius = [coder decodeDoubleForKey:@"_frontViewShadowRadius"]; + _frontViewShadowOffset = [coder decodeCGSizeForKey:@"_frontViewShadowOffset"]; + _frontViewShadowOpacity = [coder decodeDoubleForKey:@"_frontViewShadowOpacity"]; + _frontViewShadowColor = [coder decodeObjectForKey:@"_frontViewShadowColor"]; + _userInteractionStore = [coder decodeBoolForKey:@"_userInteractionStore"]; + _animationQueue = [NSMutableArray array]; + _draggableBorderWidth = [coder decodeDoubleForKey:@"_draggableBorderWidth"]; + _clipsViewsToBounds = [coder decodeBoolForKey:@"_clipsViewsToBounds"]; + _extendsPointInsideHit = [coder decodeBoolForKey:@"_extendsPointInsideHit"]; + + [self setRearViewController:[coder decodeObjectForKey:@"_rearViewController"]]; + [self setFrontViewController:[coder decodeObjectForKey:@"_frontViewController"]]; + [self setRightViewController:[coder decodeObjectForKey:@"_rightViewController"]]; + + [self setFrontViewPosition:[coder decodeIntForKey: @"_frontViewPosition"]]; + + [super decodeRestorableStateWithCoder:coder]; +} + + +- (void)applicationFinishedRestoringState +{ + // nothing to do at this stage +} + + +@end + + +#pragma mark - UIViewController(SWRevealViewController) Category + +@implementation UIViewController(SWRevealViewController) + +- (SWRevealViewController*)revealViewController +{ + UIViewController *parent = self; + Class revealClass = [SWRevealViewController class]; + while ( nil != (parent = [parent parentViewController]) && ![parent isKindOfClass:revealClass] ) {} + return (id)parent; +} + +@end + + +#pragma mark - SWRevealViewControllerSegueSetController segue identifiers + +NSString * const SWSegueRearIdentifier = @"sw_rear"; +NSString * const SWSegueFrontIdentifier = @"sw_front"; +NSString * const SWSegueRightIdentifier = @"sw_right"; + + +#pragma mark - SWRevealViewControllerSegueSetController class + +@implementation SWRevealViewControllerSegueSetController + +- (void)perform +{ + SWRevealControllerOperation operation = SWRevealControllerOperationNone; + + NSString *identifier = self.identifier; + SWRevealViewController *rvc = self.sourceViewController; + UIViewController *dvc = self.destinationViewController; + + if ( [identifier isEqualToString:SWSegueFrontIdentifier] ) + operation = SWRevealControllerOperationReplaceFrontController; + + else if ( [identifier isEqualToString:SWSegueRearIdentifier] ) + operation = SWRevealControllerOperationReplaceRearController; + + else if ( [identifier isEqualToString:SWSegueRightIdentifier] ) + operation = SWRevealControllerOperationReplaceRightController; + + if ( operation != SWRevealControllerOperationNone ) + [rvc _performTransitionOperation:operation withViewController:dvc animated:NO]; +} + +@end + + +#pragma mark - SWRevealViewControllerSeguePushController class + +@implementation SWRevealViewControllerSeguePushController + +- (void)perform +{ + SWRevealViewController *rvc = [self.sourceViewController revealViewController]; + UIViewController *dvc = self.destinationViewController; + [rvc pushFrontViewController:dvc animated:YES]; +} + +@end + + +//#pragma mark - SWRevealViewControllerSegue Class +// +//@implementation SWRevealViewControllerSegue // DEPRECATED +// +//- (void)perform +//{ +// if ( _performBlock ) +// _performBlock( self, self.sourceViewController, self.destinationViewController ); +//} +// +//@end +// +// +//#pragma mark Storyboard support +// +//@implementation SWRevealViewController(deprecated) +// +//- (void)prepareForSegue:(SWRevealViewControllerSegue *)segue sender:(id)sender // TO REMOVE: DEPRECATED IMPLEMENTATION +//{ +// // This method is required for compatibility with SWRevealViewControllerSegue, now deprecated. +// // It can be simply removed when using SWRevealViewControllerSegueSetController and SWRevealViewControlerSeguePushController +// +// NSString *identifier = segue.identifier; +// if ( [segue isKindOfClass:[SWRevealViewControllerSegue class]] && sender == nil ) +// { +// if ( [identifier isEqualToString:SWSegueRearIdentifier] ) +// { +// segue.performBlock = ^(SWRevealViewControllerSegue* rvc_segue, UIViewController* svc, UIViewController* dvc) +// { +// [self _setRearViewController:dvc animated:NO]; +// }; +// } +// else if ( [identifier isEqualToString:SWSegueFrontIdentifier] ) +// { +// segue.performBlock = ^(SWRevealViewControllerSegue* rvc_segue, UIViewController* svc, UIViewController* dvc) +// { +// [self _setFrontViewController:dvc animated:NO]; +// }; +// } +// else if ( [identifier isEqualToString:SWSegueRightIdentifier] ) +// { +// segue.performBlock = ^(SWRevealViewControllerSegue* rvc_segue, UIViewController* svc, UIViewController* dvc) +// { +// [self _setRightViewController:dvc animated:NO]; +// }; +// } +// } +//} +// +//@end + + diff --git a/Limelight/ViewControllers/SettingsViewController.h b/Limelight/ViewControllers/SettingsViewController.h new file mode 100644 index 00000000..704f97ae --- /dev/null +++ b/Limelight/ViewControllers/SettingsViewController.h @@ -0,0 +1,20 @@ +// +// SettingsViewController.h +// Limelight +// +// Created by Diego Waxemberg on 10/27/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import +#import "AppDelegate.h" + +@interface SettingsViewController : UIViewController +@property (strong, nonatomic) IBOutlet UILabel *bitrateLabel; +@property (strong, nonatomic) IBOutlet UISlider *bitrateSlider; +@property (strong, nonatomic) IBOutlet UISegmentedControl *framerateSelector; +@property (strong, nonatomic) IBOutlet UISegmentedControl *resolutionSelector; + +- (void) saveSettings; + +@end diff --git a/Limelight/ViewControllers/SettingsViewController.m b/Limelight/ViewControllers/SettingsViewController.m new file mode 100644 index 00000000..667283ec --- /dev/null +++ b/Limelight/ViewControllers/SettingsViewController.m @@ -0,0 +1,64 @@ +// +// SettingsViewController.m +// Limelight +// +// Created by Diego Waxemberg on 10/27/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import "SettingsViewController.h" +#import "Settings.h" +#import "DataManager.h" + +#define BITRATE_INTERVAL 100 + +@implementation SettingsViewController { + NSInteger _bitrate; +} +static NSString* bitrateFormat = @"Bitrate: %d bps"; + + +- (void)viewDidLoad { + [super viewDidLoad]; + + DataManager* dataMan = [[DataManager alloc] init]; + Settings* currentSettings = [dataMan retrieveSettings]; + + _bitrate = [currentSettings.bitrate integerValue]; + NSInteger framerate = [currentSettings.framerate integerValue] == 30 ? 0 : 1; + NSInteger resolution = [currentSettings.height integerValue] == 720 ? 0 : 1; + + [self.resolutionSelector setSelectedSegmentIndex:resolution]; + [self.framerateSelector setSelectedSegmentIndex:framerate]; + [self.bitrateSlider setValue:_bitrate animated:YES]; + [self.bitrateSlider addTarget:self action:@selector(bitrateSliderMoved) forControlEvents:UIControlEventValueChanged]; + [self.bitrateLabel setText:[NSString stringWithFormat:bitrateFormat, (int)_bitrate]]; +} + +- (void) bitrateSliderMoved { + _bitrate = BITRATE_INTERVAL * floor((self.bitrateSlider.value/BITRATE_INTERVAL)+0.5); + [self.bitrateLabel setText:[NSString stringWithFormat:bitrateFormat, (int)_bitrate]]; +} + + +- (void) saveSettings { + DataManager* dataMan = [[DataManager alloc] init]; + NSInteger framerate = [self.framerateSelector selectedSegmentIndex] == 0 ? 30 : 60; + NSInteger height = [self.resolutionSelector selectedSegmentIndex] == 0 ? 720 : 1080; + NSInteger width = height == 720 ? 1280 : 1920; + [dataMan saveSettingsWithBitrate:_bitrate framerate:framerate height:height width:width]; +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + + +#pragma mark - Navigation + +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { +} + + +@end diff --git a/Limelight/ViewControllers/StreamFrameViewController.m b/Limelight/ViewControllers/StreamFrameViewController.m index 0331ee3a..fa3a4c4a 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -25,6 +25,8 @@ { [super viewDidLoad]; + [self.navigationController setNavigationBarHidden:YES animated:YES]; + [self.stageLabel setText:@"Starting App"]; [self.stageLabel sizeToFit]; self.stageLabel.center = CGPointMake(self.view.frame.size.width / 2, self.stageLabel.center.y); @@ -45,9 +47,13 @@ object:nil]; } +- (void) returnToMainFrame { + [self.navigationController popToRootViewControllerAnimated:YES]; +} + - (void)applicationWillResignActive:(NSNotification *)notification { [_streamMan stopStream]; - [self performSegueWithIdentifier:@"returnToMainFrame" sender:self]; + [self returnToMainFrame]; } - (void) connectionStarted { @@ -65,7 +71,7 @@ UIAlertController* conTermAlert = [UIAlertController alertControllerWithTitle:@"Connection Terminated" message:@"The connection terminated unexpectedly" preferredStyle:UIAlertControllerStyleAlert]; [conTermAlert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action){ - [self performSegueWithIdentifier:@"returnToMainFrame" sender:self]; + [self returnToMainFrame]; }]]; [self presentViewController:conTermAlert animated:YES completion:nil]; @@ -92,7 +98,7 @@ stageName, errorCode] preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action){ - [self performSegueWithIdentifier:@"returnToMainFrame" sender:self]; + [self returnToMainFrame]; }]]; [self presentViewController:alert animated:YES completion:nil]; } @@ -102,7 +108,7 @@ message:message preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action){ - [self performSegueWithIdentifier:@"returnToMainFrame" sender:self]; + [self returnToMainFrame]; }]]; [self presentViewController:alert animated:YES completion:nil]; } diff --git a/iPad.storyboard b/iPad.storyboard index 37d9a06f..8ab6bbdc 100644 --- a/iPad.storyboard +++ b/iPad.storyboard @@ -11,10 +11,20 @@ + + + + @@ -60,6 +70,9 @@ + + + diff --git a/iPhone.storyboard b/iPhone.storyboard index efe9f1d9..1068cadb 100644 --- a/iPhone.storyboard +++ b/iPhone.storyboard @@ -1,10 +1,10 @@ - + - + @@ -14,15 +14,119 @@ + + + - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -46,20 +150,24 @@ + + - - + + + +