diff --git a/.gitignore b/.gitignore index 14ce7f3..cb7b8d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ **/xcuserdata/ +Build diff --git a/Limelight.xcodeproj/project.pbxproj b/Limelight.xcodeproj/project.pbxproj index f4b3942..e0ebc14 100644 --- a/Limelight.xcodeproj/project.pbxproj +++ b/Limelight.xcodeproj/project.pbxproj @@ -25,8 +25,8 @@ FB290DB719B2C870004C83CF /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290DB619B2C870004C83CF /* libz.dylib */; }; FB290DB919B2C877004C83CF /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290DB819B2C877004C83CF /* libbz2.dylib */; }; FB290DC419B2E98F004C83CF /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290DC319B2E98F004C83CF /* libxml2.dylib */; }; - FB290E7919B37D81004C83CF /* MainFrame-iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB290E7819B37D81004C83CF /* MainFrame-iPad.storyboard */; }; - FB290E7B19B38036004C83CF /* MainFrame-iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB290E7A19B38036004C83CF /* MainFrame-iPhone.storyboard */; }; + FB290E7919B37D81004C83CF /* iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB290E7819B37D81004C83CF /* iPad.storyboard */; }; + FB290E7B19B38036004C83CF /* iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB290E7A19B38036004C83CF /* iPhone.storyboard */; }; FB7E794419C8B71B00A15F68 /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB7E794319C8B71B00A15F68 /* libiconv.dylib */; }; FB89462819F646E200339C8A /* CryptoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FB89460619F646E200339C8A /* CryptoManager.m */; }; FB89462919F646E200339C8A /* mkcert.c in Sources */ = {isa = PBXBuildFile; fileRef = FB89460719F646E200339C8A /* mkcert.c */; }; @@ -47,6 +47,15 @@ FB8946EB19F6AFE100339C8A /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB8946E019F6AFB800339C8A /* libcrypto.a */; }; 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 /* Host.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD3495A1A004411002D2A60 /* Host.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 */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -95,8 +104,8 @@ FB290DB619B2C870004C83CF /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; FB290DB819B2C877004C83CF /* libbz2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libbz2.dylib; path = usr/lib/libbz2.dylib; sourceTree = SDKROOT; }; FB290DC319B2E98F004C83CF /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; }; - FB290E7819B37D81004C83CF /* MainFrame-iPad.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "MainFrame-iPad.storyboard"; sourceTree = SOURCE_ROOT; }; - FB290E7A19B38036004C83CF /* MainFrame-iPhone.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "MainFrame-iPhone.storyboard"; sourceTree = SOURCE_ROOT; }; + FB290E7819B37D81004C83CF /* iPad.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = iPad.storyboard; sourceTree = SOURCE_ROOT; }; + FB290E7A19B38036004C83CF /* iPhone.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = iPhone.storyboard; sourceTree = SOURCE_ROOT; }; FB7E794319C8B71B00A15F68 /* libiconv.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.dylib; path = usr/lib/libiconv.dylib; sourceTree = SDKROOT; }; FB89460519F646E200339C8A /* CryptoManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoManager.h; sourceTree = ""; }; FB89460619F646E200339C8A /* CryptoManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoManager.m; sourceTree = ""; }; @@ -211,6 +220,25 @@ FB8946E719F6AFB800339C8A /* opus_multistream.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = opus_multistream.h; sourceTree = ""; }; FB8946E819F6AFB800339C8A /* opus_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = opus_types.h; sourceTree = ""; }; 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 /* Host.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Host.h; path = Database/Host.h; sourceTree = ""; }; + FBD3495A1A004411002D2A60 /* Host.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Host.m; path = Database/Host.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 = ""; }; + FBDE86E519F82297001C18A8 /* UIAppView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIAppView.m; sourceTree = ""; }; + FBDE86E719F82315001C18A8 /* App.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = App.h; path = ../App.h; sourceTree = ""; }; + FBDE86E819F82315001C18A8 /* App.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = App.m; path = ../App.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -277,6 +305,7 @@ FB290CF019B2C406004C83CF /* Frameworks */ = { isa = PBXGroup; children = ( + FBD349571A003F05002D2A60 /* libsqlite3.dylib */, FB89468F19F6AFB800339C8A /* libs */, FB7E794319C8B71B00A15F68 /* libiconv.dylib */, FB290DC319B2E98F004C83CF /* libxml2.dylib */, @@ -299,14 +328,19 @@ FB89461519F646E200339C8A /* Stream */, FB89461E19F646E200339C8A /* Utility */, FB89462319F646E200339C8A /* ViewControllers */, + FBD3495F1A004453002D2A60 /* Database */, FB290CFA19B2C406004C83CF /* Supporting Files */, FB290D0219B2C406004C83CF /* AppDelegate.h */, FB290D0319B2C406004C83CF /* AppDelegate.m */, - FB290E7819B37D81004C83CF /* MainFrame-iPad.storyboard */, - FB290E7A19B38036004C83CF /* MainFrame-iPhone.storyboard */, + FB290E7819B37D81004C83CF /* iPad.storyboard */, + FB290E7A19B38036004C83CF /* iPhone.storyboard */, FB89463719F6473800339C8A /* Launch Screen.xib */, FB290D0819B2C406004C83CF /* Images.xcassets */, FB290D0519B2C406004C83CF /* Limelight.xcdatamodeld */, + FBDE86DE19F7A837001C18A8 /* UIComputerView.h */, + FBDE86DF19F7A837001C18A8 /* UIComputerView.m */, + FBDE86E419F82297001C18A8 /* UIAppView.h */, + FBDE86E519F82297001C18A8 /* UIAppView.m */, ); path = Limelight; sourceTree = ""; @@ -371,6 +405,8 @@ FB89461219F646E200339C8A /* MDNSManager.m */, FB89461319F646E200339C8A /* PairManager.h */, FB89461419F646E200339C8A /* PairManager.m */, + FBD3494119FC9C04002D2A60 /* AppManager.h */, + FBD3494219FC9C04002D2A60 /* AppManager.m */, ); path = Network; sourceTree = ""; @@ -395,6 +431,8 @@ children = ( FB89461F19F646E200339C8A /* Computer.h */, FB89462019F646E200339C8A /* Computer.m */, + FBDE86E719F82315001C18A8 /* App.h */, + FBDE86E819F82315001C18A8 /* App.m */, FB89462119F646E200339C8A /* Utils.h */, FB89462219F646E200339C8A /* Utils.m */, ); @@ -404,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 = ""; @@ -573,6 +615,19 @@ path = lib; sourceTree = ""; }; + FBD3495F1A004453002D2A60 /* Database */ = { + isa = PBXGroup; + children = ( + FBD3495C1A004412002D2A60 /* Settings.h */, + FBD3495D1A004412002D2A60 /* Settings.m */, + FBD349591A004411002D2A60 /* Host.h */, + FBD3495A1A004411002D2A60 /* Host.m */, + FBD349601A0089F6002D2A60 /* DataManager.h */, + FBD349611A0089F6002D2A60 /* DataManager.m */, + ); + name = Database; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -668,9 +723,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - FB290E7919B37D81004C83CF /* MainFrame-iPad.storyboard in Resources */, + FB290E7919B37D81004C83CF /* iPad.storyboard in Resources */, FB290CFE19B2C406004C83CF /* InfoPlist.strings in Resources */, - FB290E7B19B38036004C83CF /* MainFrame-iPhone.storyboard in Resources */, + FB290E7B19B38036004C83CF /* iPhone.storyboard in Resources */, FB290D0919B2C406004C83CF /* Images.xcassets in Resources */, FB89463819F6473800339C8A /* Launch Screen.xib in Resources */, ); @@ -693,21 +748,30 @@ 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 */, FB89463319F646E200339C8A /* Computer.m in Sources */, FB89462F19F646E200339C8A /* Connection.m in Sources */, FB89462919F646E200339C8A /* mkcert.c in Sources */, + 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 /* Host.m in Sources */, FB89463519F646E200339C8A /* MainFrameViewController.m in Sources */, FB89463619F646E200339C8A /* StreamFrameViewController.m in Sources */, FB89462819F646E200339C8A /* CryptoManager.m in Sources */, FB89462E19F646E200339C8A /* PairManager.m in Sources */, 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/App.h b/Limelight/App.h new file mode 100644 index 0000000..2c35cea --- /dev/null +++ b/Limelight/App.h @@ -0,0 +1,17 @@ +// +// App.h +// Limelight +// +// Created by Diego Waxemberg on 10/22/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import + +@interface App : NSObject + +@property NSString* appId; +@property NSString* appName; +@property UIImage* appImage; + +@end diff --git a/Limelight/App.m b/Limelight/App.m new file mode 100644 index 0000000..f83476d --- /dev/null +++ b/Limelight/App.m @@ -0,0 +1,15 @@ +// +// App.m +// Limelight +// +// Created by Diego Waxemberg on 10/22/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import "App.h" +#import "HttpManager.h" + +@implementation App +@synthesize appId, appName, appImage; + +@end diff --git a/Limelight/AppDelegate.h b/Limelight/AppDelegate.h index 3e323b4..8d06c52 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 680aa5f..18fabba 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 0000000..88450bc --- /dev/null +++ b/Limelight/Database/DataManager.h @@ -0,0 +1,24 @@ +// +// 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 "Host.h" + +@interface DataManager : NSObject + +@property (strong, nonatomic) AppDelegate* appDelegate; + +- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width; +- (Settings*) retrieveSettings; +- (NSArray*) retrieveHosts; +- (void) saveHosts; +- (Host*) createHost:(NSString*)name hostname:(NSString*)address; + +@end diff --git a/Limelight/Database/DataManager.m b/Limelight/Database/DataManager.m new file mode 100644 index 0000000..13f9f97 --- /dev/null +++ b/Limelight/Database/DataManager.m @@ -0,0 +1,90 @@ +// +// 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]; + NSError* error; + if (![[self.appDelegate managedObjectContext] save:&error]) { + NSLog(@"ERROR: Unable to save settings to database"); + } + [self.appDelegate saveContext]; +} + +- (Settings*) retrieveSettings { + NSArray* fetchedRecords = [self fetchRecords:@"Settings"]; + if (fetchedRecords.count == 0) { + // create a new settings object with the default values + NSEntityDescription* entity = [NSEntityDescription entityForName:@"Settings" inManagedObjectContext:[self.appDelegate managedObjectContext]]; + 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]; + } +} + +- (Host*) createHost:(NSString*)name hostname:(NSString*)address { + NSEntityDescription* entity = [NSEntityDescription entityForName:@"Host" inManagedObjectContext:[self.appDelegate managedObjectContext]]; + Host* host = [[Host alloc] initWithEntity:entity insertIntoManagedObjectContext:[self.appDelegate managedObjectContext]]; + + host.name = name; + host.address = address; + return host; +} + +- (void) saveHosts { + NSError* error; + if (![[self.appDelegate managedObjectContext] save:&error]) { + NSLog(@"ERROR: Unable to save hosts to database"); + } + [self.appDelegate saveContext]; +} + +- (NSArray*) retrieveHosts { + return [self fetchRecords:@"Host"]; +} + +- (NSArray*) fetchRecords:(NSString*)entityName { + NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init]; + NSEntityDescription* entity = [NSEntityDescription entityForName:entityName 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]; + //TODO: handle errors + + return fetchedRecords; + +} + +@end + diff --git a/Limelight/Database/Host.h b/Limelight/Database/Host.h new file mode 100644 index 0000000..c63b4c4 --- /dev/null +++ b/Limelight/Database/Host.h @@ -0,0 +1,18 @@ +// +// Host.h +// Limelight +// +// Created by Diego Waxemberg on 10/28/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import +#import + + +@interface Host : NSManagedObject + +@property (nonatomic, retain) NSString * address; +@property (nonatomic, retain) NSString * name; + +@end diff --git a/Limelight/Database/Host.m b/Limelight/Database/Host.m new file mode 100644 index 0000000..7413518 --- /dev/null +++ b/Limelight/Database/Host.m @@ -0,0 +1,17 @@ +// +// Host.m +// Limelight +// +// Created by Diego Waxemberg on 10/28/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import "Host.h" + + +@implementation Host + +@dynamic address; +@dynamic name; + +@end diff --git a/Limelight/Database/Settings.h b/Limelight/Database/Settings.h new file mode 100644 index 0000000..1198a2c --- /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 0000000..001f8ca --- /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/AddComputer.imageset/Contents.json b/Limelight/Images.xcassets/AddComputer.imageset/Contents.json new file mode 100644 index 0000000..142a64c --- /dev/null +++ b/Limelight/Images.xcassets/AddComputer.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "limelight_computer_add_1x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "limelight_computer_add_2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "limelight_computer_add_3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Limelight/Images.xcassets/AddComputer.imageset/limelight_computer_add_1x.png b/Limelight/Images.xcassets/AddComputer.imageset/limelight_computer_add_1x.png new file mode 100644 index 0000000..ae65f59 Binary files /dev/null and b/Limelight/Images.xcassets/AddComputer.imageset/limelight_computer_add_1x.png differ diff --git a/Limelight/Images.xcassets/AddComputer.imageset/limelight_computer_add_2x.png b/Limelight/Images.xcassets/AddComputer.imageset/limelight_computer_add_2x.png new file mode 100644 index 0000000..77eab3b Binary files /dev/null and b/Limelight/Images.xcassets/AddComputer.imageset/limelight_computer_add_2x.png differ diff --git a/Limelight/Images.xcassets/AddComputer.imageset/limelight_computer_add_3x.png b/Limelight/Images.xcassets/AddComputer.imageset/limelight_computer_add_3x.png new file mode 100644 index 0000000..a63a263 Binary files /dev/null and b/Limelight/Images.xcassets/AddComputer.imageset/limelight_computer_add_3x.png differ diff --git a/Limelight/Images.xcassets/AddComputerIcon.imageset/Contents.json b/Limelight/Images.xcassets/AddComputerIcon.imageset/Contents.json new file mode 100644 index 0000000..86541cf --- /dev/null +++ b/Limelight/Images.xcassets/AddComputerIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "limelight_computer_add_icon_2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Limelight/Images.xcassets/AddComputerIcon.imageset/limelight_computer_add_icon_2x.png b/Limelight/Images.xcassets/AddComputerIcon.imageset/limelight_computer_add_icon_2x.png new file mode 100644 index 0000000..661eddd Binary files /dev/null and b/Limelight/Images.xcassets/AddComputerIcon.imageset/limelight_computer_add_icon_2x.png differ diff --git a/Limelight/Images.xcassets/Computer.imageset/Contents.json b/Limelight/Images.xcassets/Computer.imageset/Contents.json new file mode 100644 index 0000000..3a06eed --- /dev/null +++ b/Limelight/Images.xcassets/Computer.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "limelight_computer_1x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "limelight_computer_2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "limelight_computer_3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Limelight/Images.xcassets/Computer.imageset/limelight_computer_1x.png b/Limelight/Images.xcassets/Computer.imageset/limelight_computer_1x.png new file mode 100644 index 0000000..7c2e71f Binary files /dev/null and b/Limelight/Images.xcassets/Computer.imageset/limelight_computer_1x.png differ diff --git a/Limelight/Images.xcassets/Computer.imageset/limelight_computer_2x.png b/Limelight/Images.xcassets/Computer.imageset/limelight_computer_2x.png new file mode 100644 index 0000000..25468de Binary files /dev/null and b/Limelight/Images.xcassets/Computer.imageset/limelight_computer_2x.png differ diff --git a/Limelight/Images.xcassets/Computer.imageset/limelight_computer_3x.png b/Limelight/Images.xcassets/Computer.imageset/limelight_computer_3x.png new file mode 100644 index 0000000..e93d417 Binary files /dev/null and b/Limelight/Images.xcassets/Computer.imageset/limelight_computer_3x.png differ diff --git a/Limelight/Images.xcassets/NoAppImage.imageset/Contents.json b/Limelight/Images.xcassets/NoAppImage.imageset/Contents.json new file mode 100644 index 0000000..05a6f1b --- /dev/null +++ b/Limelight/Images.xcassets/NoAppImage.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "limelight_no_app_image_2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Limelight/Images.xcassets/NoAppImage.imageset/limelight_no_app_image_2x.png b/Limelight/Images.xcassets/NoAppImage.imageset/limelight_no_app_image_2x.png new file mode 100644 index 0000000..c043cd1 Binary files /dev/null and b/Limelight/Images.xcassets/NoAppImage.imageset/limelight_no_app_image_2x.png differ diff --git a/Limelight/Images.xcassets/Settings.imageset/Contents.json b/Limelight/Images.xcassets/Settings.imageset/Contents.json new file mode 100644 index 0000000..00e6501 --- /dev/null +++ b/Limelight/Images.xcassets/Settings.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "settings_2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Limelight/Images.xcassets/Settings.imageset/settings_2x.png b/Limelight/Images.xcassets/Settings.imageset/settings_2x.png new file mode 100644 index 0000000..bb5b00c Binary files /dev/null and b/Limelight/Images.xcassets/Settings.imageset/settings_2x.png differ diff --git a/Limelight/Limelight-Info.plist b/Limelight/Limelight-Info.plist index d7c44f9..3067f96 100644 --- a/Limelight/Limelight-Info.plist +++ b/Limelight/Limelight-Info.plist @@ -29,9 +29,9 @@ UILaunchStoryboardName Launch Screen UIMainStoryboardFile - MainFrame-iPhone + iPhone UIMainStoryboardFile~ipad - MainFrame-iPad + iPad UIRequiredDeviceCapabilities armv7 @@ -44,8 +44,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown UISupportedInterfaceOrientations~ipad diff --git a/Limelight/Limelight.xcdatamodeld/Limelight.xcdatamodel/contents b/Limelight/Limelight.xcdatamodeld/Limelight.xcdatamodel/contents index 193f33c..b8b065e 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/Network/AppManager.h b/Limelight/Network/AppManager.h new file mode 100644 index 0000000..d70b103 --- /dev/null +++ b/Limelight/Network/AppManager.h @@ -0,0 +1,23 @@ +// +// AppManager.h +// Limelight +// +// Created by Diego Waxemberg on 10/25/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import +#import "App.h" +#import "HttpManager.h" + +@protocol AppAssetCallback + +- (void) receivedAssetForApp:(App*)app; + +@end + +@interface AppManager : NSObject + ++ (void) retrieveAppAssets:(NSArray*)apps withManager:(HttpManager*)hMan andCallback:(id)callback; + +@end diff --git a/Limelight/Network/AppManager.m b/Limelight/Network/AppManager.m new file mode 100644 index 0000000..df0c4b2 --- /dev/null +++ b/Limelight/Network/AppManager.m @@ -0,0 +1,45 @@ +// +// AppManager.m +// Limelight +// +// Created by Diego Waxemberg on 10/25/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import "AppManager.h" + +@implementation AppManager { + App* _app; + HttpManager* _hMan; + id _callback; +} + ++ (void) retrieveAppAssets:(NSArray*)apps withManager:(HttpManager*)hMan andCallback:(id)callback { + for (App* app in apps) { + AppManager* manager = [[AppManager alloc] initWithApp:app httpManager:hMan andCallback:callback]; + [manager retrieveAsset]; + } + +} + +- (id) initWithApp:(App*)app httpManager:(HttpManager*)hMan andCallback:(id)callback { + self = [super init]; + _app = app; + _hMan = hMan; + _callback = callback; + return self; +} + +- (void) retrieveAsset { + NSData* appAsset = [_hMan executeRequestSynchronously:[_hMan newAppAssetRequestWithAppId:_app.appId]]; + UIImage* appImage = [UIImage imageWithData:appAsset]; + _app.appImage = appImage; + NSLog(@"App Name: %@ id:%@ image: %@", _app.appName, _app.appId, _app.appImage); + [self performSelectorOnMainThread:@selector(sendCallBack) withObject:self waitUntilDone:NO]; +} + +- (void) sendCallBack { + [_callback receivedAssetForApp:_app]; +} + +@end diff --git a/Limelight/Network/HttpManager.h b/Limelight/Network/HttpManager.h index d24f6b4..c7c4b36 100644 --- a/Limelight/Network/HttpManager.h +++ b/Limelight/Network/HttpManager.h @@ -10,6 +10,7 @@ @interface HttpManager : NSObject ++ (NSArray*) getAppListFromXML:(NSData*)xml; + (NSString*) getStringFromXML:(NSData*)xml tag:(NSString*)tag; + (NSString*) getStatusStringFromXML:(NSData*)xml; @@ -24,6 +25,7 @@ - (NSURLRequest*) newServerInfoRequest; - (NSURLRequest*) newLaunchRequest:(NSString*)appId width:(int)width height:(int)height refreshRate:(int)refreshRate rikey:(NSString*)rikey rikeyid:(int)rikeyid; - (NSURLRequest*) newResumeRequestWithRiKey:(NSString*)riKey riKeyId:(int)riKeyId; +- (NSURLRequest*) newAppAssetRequestWithAppId:(NSString*)appId; - (NSData*) executeRequestSynchronously:(NSURLRequest*)request; @end diff --git a/Limelight/Network/HttpManager.m b/Limelight/Network/HttpManager.m index 76f9a8f..3719607 100644 --- a/Limelight/Network/HttpManager.m +++ b/Limelight/Network/HttpManager.m @@ -8,6 +8,7 @@ #import "HttpManager.h" #import "CryptoManager.h" +#import "App.h" #include #include @@ -25,6 +26,60 @@ static const NSString* PORT = @"47984"; ++ (NSArray*) getAppListFromXML:(NSData*)xml { + xmlDocPtr docPtr = xmlParseMemory([xml bytes], (int)[xml length]); + + if (docPtr == NULL) { + NSLog(@"ERROR: An error occured trying to parse xml."); + return NULL; + } + + xmlNodePtr node; + xmlNodePtr rootNode = node = xmlDocGetRootElement(docPtr); + + // Check root status_code + if (![HttpManager verifyStatus: rootNode]) { + NSLog(@"ERROR: Request returned with failure status"); + return NULL; + } + + // Skip the root node + node = node->children; + + NSMutableArray* appList = [[NSMutableArray alloc] init]; + + while (node != NULL) { + NSLog(@"node: %s", node->name); + if (!xmlStrcmp(node->name, (const xmlChar*)"App")) { + xmlNodePtr appInfoNode = node->xmlChildrenNode; + NSString* appName; + NSString* appId; + while (appInfoNode != NULL) { + NSLog(@"appInfoNode: %s", appInfoNode->name); + if (!xmlStrcmp(appInfoNode->name, (const xmlChar*)"AppTitle")) { + xmlChar* nodeVal = xmlNodeListGetString(docPtr, appInfoNode->xmlChildrenNode, 1); + appName = [[NSString alloc] initWithCString:(const char*)nodeVal encoding:NSUTF8StringEncoding]; + xmlFree(nodeVal); + } else if (!xmlStrcmp(appInfoNode->name, (const xmlChar*)"ID")) { + xmlChar* nodeVal = xmlNodeListGetString(docPtr, appInfoNode->xmlChildrenNode, 1); + appId = [[NSString alloc] initWithCString:(const char*)nodeVal encoding:NSUTF8StringEncoding]; + xmlFree(nodeVal); + } + appInfoNode = appInfoNode->next; + } + App* app = [[App alloc] init]; + app.appName = appName; + app.appId = appId; + [appList addObject:app]; + } + node = node->next; + } + xmlFree(rootNode); + xmlFree(docPtr); + + return appList; +} + + (NSString*) getStatusStringFromXML:(NSData*)xml { xmlDocPtr docPtr = xmlParseMemory([xml bytes], (int)[xml length]); @@ -198,6 +253,11 @@ static const NSString* PORT = @"47984"; return [self createRequestFromString:urlString enableTimeout:FALSE]; } +- (NSURLRequest*) newAppAssetRequestWithAppId:(NSString *)appId { + NSString* urlString = [NSString stringWithFormat:@"%@/appasset?uniqueid=%@&appid=%@&AssetType=2&AssetIdx=0", _baseURL, _uniqueId, appId]; + return [self createRequestFromString:urlString enableTimeout:FALSE]; +} + - (NSString*) bytesToHex:(NSData*)data { const unsigned char* bytes = [data bytes]; NSMutableString *hex = [[NSMutableString alloc] init]; @@ -217,7 +277,11 @@ static const NSString* PORT = @"47984"; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { - _requestResp = [HttpManager fixXmlVersion:_respData]; + if ([[NSString alloc] initWithData:_respData encoding:NSUTF8StringEncoding] != nil) { + _requestResp = [HttpManager fixXmlVersion:_respData]; + } else { + _requestResp = _respData; + } dispatch_semaphore_signal(_requestLock); } diff --git a/Limelight/Network/PairManager.h b/Limelight/Network/PairManager.h index 12cd30e..0dc3f45 100644 --- a/Limelight/Network/PairManager.h +++ b/Limelight/Network/PairManager.h @@ -14,6 +14,7 @@ - (void) showPIN:(NSString*)PIN; - (void) pairSuccessful; - (void) pairFailed:(NSString*)message; +- (void) alreadyPaired; @end diff --git a/Limelight/Network/PairManager.m b/Limelight/Network/PairManager.m index b4c6488..0f72ecf 100644 --- a/Limelight/Network/PairManager.m +++ b/Limelight/Network/PairManager.m @@ -32,14 +32,13 @@ [_callback pairFailed:@"Unable to connect to PC"]; return; } - if (![[HttpManager getStringFromXML:serverInfo tag:@"currentgame"] isEqual:@"0"]) { [_callback pairFailed:@"You must stop streaming before attempting to pair."]; } else if (![[HttpManager getStringFromXML:serverInfo tag:@"PairStatus"] isEqual:@"1"]) { [self initiatePair]; } else { - [_callback pairFailed:@"This device is already paired."]; + [_callback alreadyPaired]; } } diff --git a/Limelight/Stream/StreamConfiguration.h b/Limelight/Stream/StreamConfiguration.h index 6129ed3..0490a0a 100644 --- a/Limelight/Stream/StreamConfiguration.h +++ b/Limelight/Stream/StreamConfiguration.h @@ -11,6 +11,7 @@ @interface StreamConfiguration : NSObject @property NSString* host; +@property NSString* appID; @property int hostAddr; @property int width; @property int height; diff --git a/Limelight/Stream/StreamConfiguration.m b/Limelight/Stream/StreamConfiguration.m index b2607bb..ea6ac30 100644 --- a/Limelight/Stream/StreamConfiguration.m +++ b/Limelight/Stream/StreamConfiguration.m @@ -9,5 +9,5 @@ #import "StreamConfiguration.h" @implementation StreamConfiguration -@synthesize host, hostAddr, width, height, frameRate, bitRate, riKeyId, riKey; +@synthesize host, appID, hostAddr, width, height, frameRate, bitRate, riKeyId, riKey; @end diff --git a/Limelight/Stream/StreamManager.m b/Limelight/Stream/StreamManager.m index d35d19f..bc64800 100644 --- a/Limelight/Stream/StreamManager.m +++ b/Limelight/Stream/StreamManager.m @@ -42,6 +42,7 @@ NSData* serverInfoResp = [hMan executeRequestSynchronously:[hMan newServerInfoRequest]]; NSString* currentGame = [HttpManager getStringFromXML:serverInfoResp tag:@"currentgame"]; NSString* pairStatus = [HttpManager getStringFromXML:serverInfoResp tag:@"PairStatus"]; + NSString* currentClient = [HttpManager getStringFromXML:serverInfoResp tag:@"CurrentClient"]; if (currentGame == NULL || pairStatus == NULL) { [_callbacks launchFailed:@"Failed to connect to PC"]; return; @@ -55,6 +56,11 @@ // resumeApp and launchApp handle calling launchFailed if (![currentGame isEqualToString:@"0"]) { + if (![currentClient isEqualToString:@"1"]) { + // The server is streaming to someone else + [_callbacks launchFailed:@"There is another stream in progress"]; + return; + } // App already running, resume it if (![self resumeApp:hMan]) { return; @@ -79,7 +85,7 @@ - (BOOL) launchApp:(HttpManager*)hMan { NSData* launchResp = [hMan executeRequestSynchronously: - [hMan newLaunchRequest:@"67339056" + [hMan newLaunchRequest:_config.appID width:_config.width height:_config.height refreshRate:_config.frameRate diff --git a/Limelight/UIAppView.h b/Limelight/UIAppView.h new file mode 100644 index 0000000..9b202d5 --- /dev/null +++ b/Limelight/UIAppView.h @@ -0,0 +1,23 @@ +// +// UIAppView.h +// Limelight +// +// Created by Diego Waxemberg on 10/22/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import +#import "App.h" + +@protocol AppCallback + +- (void) appClicked:(App*) app; + +@end + +@interface UIAppView : UIView + +- (id) initWithApp:(App*)app andCallback:(id)callback; +- (void) updateAppImage; + +@end diff --git a/Limelight/UIAppView.m b/Limelight/UIAppView.m new file mode 100644 index 0000000..645d65d --- /dev/null +++ b/Limelight/UIAppView.m @@ -0,0 +1,72 @@ +// +// UIAppView.m +// Limelight +// +// Created by Diego Waxemberg on 10/22/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import "UIAppView.h" + +@implementation UIAppView { + App* _app; + UIButton* _appButton; + UILabel* _appLabel; + id _callback; +} +static int LABEL_DY = 20; + +- (id) initWithApp:(App*)app andCallback:(id)callback { + self = [super init]; + _app = app; + _callback = callback; + + _appButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [_appButton setContentEdgeInsets:UIEdgeInsetsMake(0, 4, 0, 4)]; + [_appButton setBackgroundImage:[UIImage imageNamed:@"NoAppImage"] forState:UIControlStateNormal]; + [_appButton sizeToFit]; + [_appButton addTarget:self action:@selector(appClicked) forControlEvents:UIControlEventTouchUpInside]; + _appButton.layer.shadowColor = [[UIColor blackColor] CGColor]; + _appButton.layer.shadowOffset = CGSizeMake(5,8); + _appButton.layer.shadowOpacity = 0.7; + + _appLabel = [[UILabel alloc] init]; + [_appLabel setText:_app.appName]; + [_appLabel sizeToFit]; + _appLabel.center = CGPointMake(_appButton.bounds.origin.x + (_appButton.bounds.size.width / 2), _appButton.bounds.origin.y + _appButton.bounds.size.height + LABEL_DY); + + [self updateBounds]; + [self addSubview:_appButton]; + [self addSubview:_appLabel]; + + + return self; +} + +- (void) updateBounds { + float x = _appButton.frame.origin.x < _appLabel.frame.origin.x ? _appButton.frame.origin.x : _appLabel.frame.origin.x; + float y = _appButton.frame.origin.y < _appLabel.frame.origin.y ? _appButton.frame.origin.y : _appLabel.frame.origin.y; + self.bounds = CGRectMake(x , y, _appButton.frame.size.width > _appLabel.frame.size.width ? _appButton.frame.size.width : _appLabel.frame.size.width, _appButton.frame.size.height + _appLabel.frame.size.height + LABEL_DY / 2); + self.frame = CGRectMake(x , y, _appButton.frame.size.width > _appLabel.frame.size.width ? _appButton.frame.size.width : _appLabel.frame.size.width, _appButton.frame.size.height + _appLabel.frame.size.height + LABEL_DY / 2); +} + +- (void) appClicked { + [_callback appClicked:_app]; +} + +- (void) updateAppImage { + if (_app.appImage != nil) { + [_appButton setBackgroundImage:_app.appImage forState:UIControlStateNormal]; + [self setNeedsDisplay]; + } +} + +/* +// Only override drawRect: if you perform custom drawing. +// An empty implementation adversely affects performance during animation. +- (void)drawRect:(CGRect)rect { + // Drawing code +} +*/ + +@end diff --git a/Limelight/UIComputerView.h b/Limelight/UIComputerView.h new file mode 100644 index 0000000..74b282a --- /dev/null +++ b/Limelight/UIComputerView.h @@ -0,0 +1,24 @@ +// +// UIComputerView.h +// Limelight +// +// Created by Diego Waxemberg on 10/22/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import +#import "Computer.h" + +@protocol HostCallback + +- (void) hostClicked:(Computer*)computer; +- (void) addHostClicked; + +@end + +@interface UIComputerView : UIView + +- (id) initWithComputer:(Computer*)computer andCallback:(id)callback; +- (id) initForAddWithCallback:(id)callback; + +@end diff --git a/Limelight/UIComputerView.m b/Limelight/UIComputerView.m new file mode 100644 index 0000000..b97b91e --- /dev/null +++ b/Limelight/UIComputerView.m @@ -0,0 +1,99 @@ +// +// UIComputerView.m +// Limelight +// +// Created by Diego Waxemberg on 10/22/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import "UIComputerView.h" + +@implementation UIComputerView { + Computer* _computer; + UIButton* _hostButton; + UILabel* _hostLabel; + id _callback; + CGSize _labelSize; +} +static int LABEL_DY = 20; + +- (id) init { + self = [super init]; + _hostButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [_hostButton setContentEdgeInsets:UIEdgeInsetsMake(0, 4, 0, 4)]; + [_hostButton setBackgroundImage:[UIImage imageNamed:@"Computer"] forState:UIControlStateNormal]; + [_hostButton sizeToFit]; + + _hostButton.layer.shadowColor = [[UIColor blackColor] CGColor]; + _hostButton.layer.shadowOffset = CGSizeMake(5,8); + _hostButton.layer.shadowOpacity = 0.7; + + _hostLabel = [[UILabel alloc] init]; + + return self; +} + +- (id) initWithComputer:(Computer*)computer andCallback:(id)callback { + self = [self init]; + _computer = computer; + _callback = callback; + + [_hostLabel setText:[_computer displayName]]; + [_hostLabel sizeToFit]; + [_hostButton addTarget:self action:@selector(hostClicked) forControlEvents:UIControlEventTouchUpInside]; + _hostLabel.center = CGPointMake(_hostButton.frame.origin.x + (_hostButton.frame.size.width / 2), _hostButton.frame.origin.y + _hostButton.frame.size.height + LABEL_DY); + [self updateBounds]; + [self addSubview:_hostButton]; + [self addSubview:_hostLabel]; + + return self; +} + +- (void) updateBounds { + float x = _hostButton.frame.origin.x < _hostLabel.frame.origin.x ? _hostButton.frame.origin.x : _hostLabel.frame.origin.x; + float y = _hostButton.frame.origin.y < _hostLabel.frame.origin.y ? _hostButton.frame.origin.y : _hostLabel.frame.origin.y; + self.bounds = CGRectMake(x , y, _hostButton.frame.size.width > _hostLabel.frame.size.width ? _hostButton.frame.size.width : _hostLabel.frame.size.width, _hostButton.frame.size.height + _hostLabel.frame.size.height + LABEL_DY / 2); + self.frame = CGRectMake(x , y, _hostButton.frame.size.width > _hostLabel.frame.size.width ? _hostButton.frame.size.width : _hostLabel.frame.size.width, _hostButton.frame.size.height + _hostLabel.frame.size.height + LABEL_DY / 2); +} + +- (id) initForAddWithCallback:(id)callback { + self = [self init]; + _callback = callback; + + [_hostButton setBackgroundImage:[UIImage imageNamed:@"Computer"] forState:UIControlStateNormal]; + [_hostButton sizeToFit]; + [_hostButton addTarget:self action:@selector(addClicked) forControlEvents:UIControlEventTouchUpInside]; + + [_hostLabel setText:@"Add Host"]; + [_hostLabel sizeToFit]; + _hostLabel.center = CGPointMake(_hostButton.frame.origin.x + (_hostButton.frame.size.width / 2), _hostButton.frame.origin.y + _hostButton.frame.size.height + LABEL_DY); + + UIImageView* addIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"AddComputerIcon"]]; + [addIcon sizeToFit]; + addIcon.center = CGPointMake(_hostButton.frame.origin.x + _hostButton.frame.size.width, _hostButton.frame.origin.y); + + [self updateBounds]; + [self addSubview:_hostButton]; + [self addSubview:_hostLabel]; + [self addSubview:addIcon]; + + return self; +} + +- (void) hostClicked { + [_callback hostClicked:_computer]; +} + +- (void) addClicked { + [_callback addHostClicked]; +} + +/* +// Only override drawRect: if you perform custom drawing. +// An empty implementation adversely affects performance during animation. +- (void)drawRect:(CGRect)rect { + // Drawing code +} +*/ + +@end diff --git a/Limelight/Utility/Computer.h b/Limelight/Utility/Computer.h index dc0058c..854a3dd 100644 --- a/Limelight/Utility/Computer.h +++ b/Limelight/Utility/Computer.h @@ -15,6 +15,5 @@ - (id) initWithHost:(NSNetService*)host; - (id) initWithIp:(NSString*)host; -- (id) initPlaceholder; @end diff --git a/Limelight/Utility/Computer.m b/Limelight/Utility/Computer.m index fa8deb9..6a7cb22 100644 --- a/Limelight/Utility/Computer.m +++ b/Limelight/Utility/Computer.m @@ -28,13 +28,4 @@ return self; } -- (id) initPlaceholder { - self = [super init]; - - self.hostName = NULL; - self.displayName = @"No computers found"; - - return self; -} - @end diff --git a/Limelight/ViewControllers/MainFrameViewController.h b/Limelight/ViewControllers/MainFrameViewController.h index d4777f9..cc8a15b 100644 --- a/Limelight/ViewControllers/MainFrameViewController.h +++ b/Limelight/ViewControllers/MainFrameViewController.h @@ -10,16 +10,13 @@ #import "MDNSManager.h" #import "PairManager.h" #import "StreamConfiguration.h" +#import "UIComputerView.h" +#import "UIAppView.h" +#import "AppManager.h" +#import "SWRevealViewController.h" -@interface MainFrameViewController : UIViewController -@property (strong, nonatomic) IBOutlet UIPickerView *HostPicker; -- (IBAction)StreamButton:(UIButton *)sender; -- (IBAction)PairButton:(UIButton *)sender; - -@property (strong, nonatomic) IBOutlet UIPickerView *StreamConfigs; -@property (strong, nonatomic) NSArray* streamConfigVals; -@property (strong, nonatomic) NSArray* hostPickerVals; -@property (strong, nonatomic) IBOutlet UITextField *hostTextField; +@interface MainFrameViewController : UIViewController +@property (strong, nonatomic) IBOutlet UIBarButtonItem *settingsSidebarButton; + (StreamConfiguration*) getStreamConfiguration; diff --git a/Limelight/ViewControllers/MainFrameViewController.m b/Limelight/ViewControllers/MainFrameViewController.m index f1419b3..9c992e6 100644 --- a/Limelight/ViewControllers/MainFrameViewController.m +++ b/Limelight/ViewControllers/MainFrameViewController.m @@ -1,4 +1,3 @@ -// // MainFrameViewController.m // Limelight-iOS // @@ -14,47 +13,32 @@ #import "VideoDecoderRenderer.h" #import "StreamManager.h" #import "Utils.h" +#import "UIComputerView.h" +#import "UIAppView.h" +#import "App.h" +#import "SettingsViewController.h" +#import "DataManager.h" +#import "Settings.h" @implementation MainFrameViewController { NSOperationQueue* _opQueue; MDNSManager* _mDNSManager; Computer* _selectedHost; + NSString* _uniqueId; + NSData* _cert; + UIAlertView* _pairAlert; + UIScrollView* hostScrollView; + UIScrollView* appScrollView; } +static NSString* deviceName = @"roth"; +static NSMutableSet* hostList; static StreamConfiguration* streamConfig; + (StreamConfiguration*) getStreamConfiguration { return streamConfig; } -- (void)PairButton:(UIButton *)sender -{ - NSLog(@"Pair Button Pressed!"); - if ([self.hostTextField.text length] > 0) { - _selectedHost = [[Computer alloc] initWithIp:self.hostTextField.text]; - NSLog(@"Using custom host: %@", self.hostTextField.text); - } - - if (![self validatePcSelected]) { - NSLog(@"No valid PC selected"); - return; - } - - [CryptoManager generateKeyPairUsingSSl]; - NSString* uniqueId = [CryptoManager getUniqueID]; - NSData* cert = [CryptoManager readCertFromFile]; - - if ([Utils resolveHost:_selectedHost.hostName] == 0) { - [self displayDnsFailedDialog]; - return; - } - - HttpManager* hMan = [[HttpManager alloc] initWithHost:_selectedHost.hostName uniqueId:uniqueId deviceName:@"roth" cert:cert]; - PairManager* pMan = [[PairManager alloc] initWithManager:hMan andCert:cert callback:self]; - - [_opQueue addOperation:pMan]; -} - - (void)showPIN:(NSString *)PIN { dispatch_sync(dispatch_get_main_queue(), ^{ _pairAlert = [[UIAlertView alloc] initWithTitle:@"Pairing" message:[NSString stringWithFormat:@"Enter the following PIN on the host machine: %@", PIN]delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil]; @@ -78,6 +62,26 @@ static StreamConfiguration* streamConfig; }); } +- (void)alreadyPaired { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + HttpManager* hMan = [[HttpManager alloc] initWithHost:_selectedHost.hostName uniqueId:_uniqueId deviceName:deviceName cert:_cert]; + NSData* appListResp = [hMan executeRequestSynchronously:[hMan newAppListRequest]]; + NSArray* appList = [HttpManager getAppListFromXML:appListResp]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateApps:appList]; + }); + [AppManager retrieveAppAssets:appList withManager:hMan andCallback:self]; + }); +} + +- (void) receivedAssetForApp:(App*)app { + NSArray* subviews = [appScrollView subviews]; + for (UIAppView* appView in subviews) { + [appView updateAppImage]; + } +} + - (void)displayDnsFailedDialog { UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Network Error" message:@"Failed to resolve host." @@ -86,123 +90,121 @@ static StreamConfiguration* streamConfig; [self presentViewController:alert animated:YES completion:nil]; } -- (void)StreamButton:(UIButton *)sender -{ - NSLog(@"Stream Button Pressed!"); - if ([self.hostTextField.text length] > 0) { - _selectedHost = [[Computer alloc] initWithIp:self.hostTextField.text]; - NSLog(@"Using custom host: %@", self.hostTextField.text); - } - - if (![self validatePcSelected]) { - NSLog(@"No valid PC selected"); - return; - } - +- (void) hostClicked:(Computer *)computer { + NSLog(@"Clicked host: %@", computer.displayName); + _selectedHost = computer; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + HttpManager* hMan = [[HttpManager alloc] initWithHost:computer.hostName uniqueId:_uniqueId deviceName:deviceName cert:_cert]; + NSData* serverInfoResp = [hMan executeRequestSynchronously:[hMan newServerInfoRequest]]; + if ([[HttpManager getStringFromXML:serverInfoResp tag:@"PairStatus"] isEqualToString:@"1"]) { + NSLog(@"Already Paired"); + [self alreadyPaired]; + } else { + NSLog(@"Trying to pair"); + PairManager* pMan = [[PairManager alloc] initWithManager:hMan andCert:_cert callback:self]; + [_opQueue addOperation:pMan]; + } + }); +} + +- (void) addHostClicked { + NSLog(@"Clicked add host"); + UIAlertController* alertController = [UIAlertController alertControllerWithTitle:@"Host Address" message:@"Please enter a hostname or IP address" preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){ + NSString* host = ((UITextField*)[[alertController textFields] objectAtIndex:0]).text; + Computer* newHost = [[Computer alloc] initWithIp:host]; + [hostList addObject:newHost]; + [self updateHosts:[hostList allObjects]]; + DataManager* dataMan = [[DataManager alloc] init]; + [dataMan createHost:newHost.displayName hostname:newHost.hostName]; + [dataMan saveHosts]; + + //TODO: get pair state + + + }]]; + [alertController addTextFieldWithConfigurationHandler:nil]; + [self presentViewController:alertController animated:YES completion:nil]; +} + +- (void) appClicked:(App *)app { + NSLog(@"Clicked app: %@", app.appName); streamConfig = [[StreamConfiguration alloc] init]; streamConfig.host = _selectedHost.hostName; streamConfig.hostAddr = [Utils resolveHost:_selectedHost.hostName]; + streamConfig.appID = app.appId; if (streamConfig.hostAddr == 0) { [self displayDnsFailedDialog]; return; } - unsigned long selectedConf = [self.StreamConfigs selectedRowInComponent:0]; - 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; - } - 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]; -} - -- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component -{ - if (pickerView == self.StreamConfigs) { - return [self.streamConfigVals objectAtIndex:row]; - } else if (pickerView == self.HostPicker) { - return ((Computer*)([self.hostPickerVals objectAtIndex:row])).displayName; - } else { - return nil; - } -} - -- (void)setSelectedHost:(NSInteger)selectedIndex -{ - _selectedHost = (Computer*)([self.hostPickerVals objectAtIndex:selectedIndex]); - if (_selectedHost.hostName == NULL) { - // This must be the placeholder computer - _selectedHost = NULL; - } -} - -- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component -{ - if (pickerView == self.HostPicker) { - [self setSelectedHost:[self.HostPicker selectedRowInComponent:0]]; - } + DataManager* dataMan = [[DataManager alloc] init]; + Settings* streamSettings = [dataMan retrieveSettings]; - //TODO: figure out how to save this info!! + 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]; } -// returns the number of 'columns' to display. -- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView -{ - return 1; -} - -// returns the # of rows in each component.. -- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component -{ - if (pickerView == self.StreamConfigs) { - return self.streamConfigVals.count; - } else if (pickerView == self.HostPicker) { - return self.hostPickerVals.count; - } else { - return 0; +- (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]; } } - (void)viewDidLoad { [super viewDidLoad]; - - self.streamConfigVals = [[NSArray alloc] initWithObjects:@"1280x720 (30Hz)", @"1280x720 (60Hz)", @"1920x1080 (30Hz)", @"1920x1080 (60Hz)",nil]; - [self.StreamConfigs selectRow:1 inComponent:0 animated:NO]; + + // 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 - [self updateHosts:[[NSArray alloc] init]]; + // 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, 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]; + + [self retrieveSavedHosts]; + [self updateHosts:[hostList allObjects]]; + [self.view addSubview:hostScrollView]; + [self.view addSubview:appScrollView]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; + [self.navigationController setNavigationBarHidden:NO animated:YES]; _mDNSManager = [[MDNSManager alloc] initWithCallback:self]; [_mDNSManager searchForHosts]; } @@ -213,17 +215,69 @@ static StreamConfiguration* streamConfig; [_mDNSManager stopSearching]; } -- (void)updateHosts:(NSArray *)hosts { - NSMutableArray *hostPickerValues = [[NSMutableArray alloc] initWithArray:hosts]; +- (void) retrieveSavedHosts { + //TODO: Get rid of Computer and only use Host - if ([hostPickerValues count] == 0) { - [hostPickerValues addObject:[[Computer alloc] initPlaceholder]]; + DataManager* dataMan = [[DataManager alloc] init]; + NSArray* hosts = [dataMan retrieveHosts]; + for (Host* host in hosts) { + Computer* comp = [[Computer alloc] initWithIp:host.address]; + comp.displayName = host.name; + [hostList addObject:comp]; + } +} + +- (void)updateHosts:(NSArray *)hosts { + [hostList addObjectsFromArray:hosts]; + [[hostScrollView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; + UIComputerView* addComp = [[UIComputerView alloc] initForAddWithCallback:self]; + UIComputerView* compView; + float prevEdge = -1; + for (Computer* comp in hostList) { + compView = [[UIComputerView alloc] initWithComputer:comp andCallback:self]; + compView.center = CGPointMake([self getCompViewX:compView addComp:addComp prevEdge:prevEdge], hostScrollView.frame.size.height / 2); + prevEdge = compView.frame.origin.x + compView.frame.size.width; + [hostScrollView addSubview:compView]; } - self.hostPickerVals = hostPickerValues; - [self.HostPicker reloadAllComponents]; + prevEdge = [self getCompViewX:addComp addComp:addComp prevEdge:prevEdge]; + addComp.center = CGPointMake(prevEdge, hostScrollView.frame.size.height / 2); - [self setSelectedHost:[self.HostPicker selectedRowInComponent:0]]; + [hostScrollView addSubview:addComp]; + [hostScrollView setContentSize:CGSizeMake(prevEdge + addComp.frame.size.width, hostScrollView.frame.size.height)]; +} + +- (float) getCompViewX:(UIComputerView*)comp addComp:(UIComputerView*)addComp prevEdge:(float)prevEdge { + if (prevEdge == -1) { + return hostScrollView.frame.origin.x + comp.frame.size.width / 2 + addComp.frame.size.width / 2; + } else { + return prevEdge + addComp.frame.size.width / 2 + comp.frame.size.width / 2; + } +} + +- (void) updateApps:(NSArray*)apps { + [[appScrollView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; + App* fakeApp = [[App alloc] init]; + fakeApp.appName = @"No App Name"; + UIAppView* noAppImage = [[UIAppView alloc] initWithApp:fakeApp andCallback:nil]; + float prevEdge = -1; + UIAppView* appView; + for (App* app in apps) { + appView = [[UIAppView alloc] initWithApp:app andCallback:self]; + prevEdge = [self getAppViewX:appView noApp:noAppImage prevEdge:prevEdge]; + appView.center = CGPointMake(prevEdge, appScrollView.frame.size.height / 2); + prevEdge = appView.frame.origin.x + appView.frame.size.width; + [appScrollView addSubview:appView]; + } + [appScrollView setContentSize:CGSizeMake(prevEdge + noAppImage.frame.size.width, appScrollView.frame.size.height)]; +} + +- (float) getAppViewX:(UIAppView*)app noApp:(UIAppView*)noAppImage prevEdge:(float)prevEdge { + if (prevEdge == -1) { + return appScrollView.frame.origin.x + app.frame.size.width / 2 + noAppImage.frame.size.width / 2; + } else { + return prevEdge + app.frame.size.width / 2 + noAppImage.frame.size.width / 2; + } } - (BOOL)validatePcSelected { @@ -257,22 +311,4 @@ static StreamConfiguration* streamConfig; - (BOOL)shouldAutorotate { return YES; } - -- (NSUInteger)supportedInterfaceOrientations { - NSString *deviceType = [UIDevice currentDevice].model; - if ([deviceType containsString:@"iPhone"] || [deviceType containsString:@"iPod"]) { - return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; - } else { - return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight; - } -} - -- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { - NSString *deviceType = [UIDevice currentDevice].model; - if ([deviceType containsString:@"iPhone"] || [deviceType containsString:@"iPod"]) { - return UIInterfaceOrientationPortrait; - } else { - return UIInterfaceOrientationLandscapeRight; - } -} @end diff --git a/Limelight/ViewControllers/SWRevealViewController.h b/Limelight/ViewControllers/SWRevealViewController.h new file mode 100755 index 0000000..279106e --- /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 0000000..de73c51 --- /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 0000000..704f97a --- /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 0000000..0dbc7b2 --- /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 kbps"; + + +- (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 588bee9..fa3a4c4 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -25,7 +25,11 @@ { [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); [UIApplication sharedApplication].idleTimerDisabled = YES; @@ -43,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 { @@ -53,6 +61,8 @@ dispatch_async(dispatch_get_main_queue(), ^{ [self.spinner stopAnimating]; [self.stageLabel setText:@"Waiting for first frame..."]; + [self.stageLabel sizeToFit]; + self.stageLabel.center = CGPointMake(self.view.frame.size.width / 2, self.stageLabel.center.y); }); } @@ -61,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]; @@ -74,6 +84,8 @@ NSString* lowerCase = [NSString stringWithFormat:@"%s in progress...", stageName]; NSString* titleCase = [[[lowerCase substringToIndex:1] uppercaseString] stringByAppendingString:[lowerCase substringFromIndex:1]]; [self.stageLabel setText:titleCase]; + [self.stageLabel sizeToFit]; + self.stageLabel.center = CGPointMake(self.view.frame.size.width / 2, self.stageLabel.center.y); }); } @@ -86,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]; } @@ -96,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]; } @@ -118,13 +130,4 @@ - (BOOL)shouldAutorotate { return YES; } - -- (NSUInteger)supportedInterfaceOrientations { - return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight; -} - -- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { - return UIInterfaceOrientationLandscapeRight; -} - @end diff --git a/MainFrame-iPad.storyboard b/MainFrame-iPad.storyboard deleted file mode 100644 index 9eb0431..0000000 --- a/MainFrame-iPad.storyboard +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MainFrame-iPhone.storyboard b/MainFrame-iPhone.storyboard deleted file mode 100644 index 4aecfbb..0000000 --- a/MainFrame-iPhone.storyboard +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iPad.storyboard b/iPad.storyboard new file mode 100644 index 0000000..86400a9 --- /dev/null +++ b/iPad.storyboard @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iPhone.storyboard b/iPhone.storyboard new file mode 100644 index 0000000..cd22b7f --- /dev/null +++ b/iPhone.storyboard @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +