From 77b6d304f7bf9f71885de2b321d1ec206ebd1345 Mon Sep 17 00:00:00 2001 From: Diego Waxemberg Date: Wed, 29 Oct 2014 00:47:51 -0400 Subject: [PATCH] added settings menu and persistent storage for settings --- Limelight.xcodeproj/project.pbxproj | 40 + Limelight/AppDelegate.h | 1 + Limelight/AppDelegate.m | 6 +- Limelight/Database/DataManager.h | 22 + Limelight/Database/DataManager.m | 65 + Limelight/Database/Hosts.h | 18 + Limelight/Database/Hosts.m | 17 + Limelight/Database/Settings.h | 20 + Limelight/Database/Settings.m | 19 + .../Left4Dead2.imageset/Left4Dead2-1.jpg | Bin 31418 -> 0 bytes .../Left4Dead2.imageset/Left4Dead2-2.jpg | Bin 31418 -> 0 bytes .../Left4Dead2.imageset/Left4Dead2.jpg | Bin 31418 -> 0 bytes .../Contents.json | 8 +- .../Settings.imageset/settings_2x.png | Bin 0 -> 2764 bytes .../Limelight.xcdatamodel/contents | 17 +- .../ViewControllers/MainFrameViewController.h | 4 +- .../ViewControllers/MainFrameViewController.m | 76 +- .../ViewControllers/SWRevealViewController.h | 418 ++++ .../ViewControllers/SWRevealViewController.m | 1897 +++++++++++++++++ .../ViewControllers/SettingsViewController.h | 20 + .../ViewControllers/SettingsViewController.m | 64 + .../StreamFrameViewController.m | 14 +- iPad.storyboard | 13 + iPhone.storyboard | 120 +- 24 files changed, 2803 insertions(+), 56 deletions(-) create mode 100644 Limelight/Database/DataManager.h create mode 100644 Limelight/Database/DataManager.m create mode 100644 Limelight/Database/Hosts.h create mode 100644 Limelight/Database/Hosts.m create mode 100644 Limelight/Database/Settings.h create mode 100644 Limelight/Database/Settings.m delete mode 100644 Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2-1.jpg delete mode 100644 Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2-2.jpg delete mode 100644 Limelight/Images.xcassets/Left4Dead2.imageset/Left4Dead2.jpg rename Limelight/Images.xcassets/{Left4Dead2.imageset => Settings.imageset}/Contents.json (59%) create mode 100644 Limelight/Images.xcassets/Settings.imageset/settings_2x.png create mode 100755 Limelight/ViewControllers/SWRevealViewController.h create mode 100755 Limelight/ViewControllers/SWRevealViewController.m create mode 100644 Limelight/ViewControllers/SettingsViewController.h create mode 100644 Limelight/ViewControllers/SettingsViewController.m 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 ffd4f4afcca29e0ecb248750e0ddee16980a5a0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31418 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 B-Z=mO literal 0 HcmV?d00001 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 @@ + + - - + + + +