diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..661f0a52ac --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/AsyncDisplayKit"] + path = submodules/AsyncDisplayKit + url = https://github.com/facebook/AsyncDisplayKit.git diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index d6efabdd51..b338b70377 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -10,6 +10,45 @@ D05CC2671B69316F00E235A3 /* Display.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2661B69316F00E235A3 /* Display.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; + D05CC29A1B69323B00E235A3 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */; }; + D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC29F1B69326400E235A3 /* NavigationController.swift */; }; + D05CC2A21B69326C00E235A3 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* Window.swift */; }; + D05CC2B61B69339A00E235A3 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2B21B6932E900E235A3 /* AsyncDisplayKit.framework */; }; + D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E21B69552C00E235A3 /* ViewController.swift */; }; + D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */; }; + D05CC2E81B69555800E235A3 /* CALayer+ImplicitAnimations.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E51B69555800E235A3 /* CALayer+ImplicitAnimations.m */; }; + D05CC2E91B69555800E235A3 /* CALayer+ImplicitAnimations.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2E61B69555800E235A3 /* CALayer+ImplicitAnimations.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */; }; + D05CC2ED1B69558A00E235A3 /* RuntimeUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2EB1B69558A00E235A3 /* RuntimeUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EE1B6955D000E235A3 /* UIKitUtils.swift */; }; + D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EF1B6955D000E235A3 /* UIViewController+Navigation.m */; }; + D05CC2F91B6955D000E235A3 /* UIViewController+Navigation.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2F01B6955D000E235A3 /* UIViewController+Navigation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2F11B6955D000E235A3 /* UINavigationItem+Proxy.m */; }; + D05CC2FB1B6955D000E235A3 /* UINavigationItem+Proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2F21B6955D000E235A3 /* UINavigationItem+Proxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2F31B6955D000E235A3 /* UIKitUtils.m */; }; + D05CC2FD1B6955D000E235A3 /* UIKitUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2F41B6955D000E235A3 /* UIKitUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2F51B6955D000E235A3 /* UIWindow+OrientationChange.m */; }; + D05CC2FF1B6955D000E235A3 /* UIWindow+OrientationChange.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2F61B6955D000E235A3 /* UIWindow+OrientationChange.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3011B69568600E235A3 /* NotificationCenterUtils.m */; }; + D05CC3041B69568600E235A3 /* NotificationCenterUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC3071B69575900E235A3 /* NSBag.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3051B69575900E235A3 /* NSBag.m */; }; + D05CC3081B69575900E235A3 /* NSBag.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3061B69575900E235A3 /* NSBag.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC3151B695A9600E235A3 /* NavigationTransitionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */; }; + D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30A1B695A9500E235A3 /* NavigationBar.swift */; }; + D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */; }; + D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */; }; + D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */; }; + D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */; }; + D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */; }; + D05CC31C1B695A9600E235A3 /* BarButtonItemWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */; }; + D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3111B695A9600E235A3 /* UIBarButtonItem+Proxy.m */; }; + D05CC31E1B695A9600E235A3 /* UIBarButtonItem+Proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3121B695A9600E235A3 /* UIBarButtonItem+Proxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3131B695A9600E235A3 /* NavigationControllerProxy.m */; }; + D05CC3201B695A9600E235A3 /* NavigationControllerProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3141B695A9600E235A3 /* NavigationControllerProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC3241B695B0700E235A3 /* NavigationBarProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3221B695B0700E235A3 /* NavigationBarProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */; }; + D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */; }; + D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -20,6 +59,41 @@ remoteGlobalIDString = D05CC2621B69316F00E235A3; remoteInfo = Display; }; + D05CC2AB1B6932E900E235A3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 058D09AC195D04C000B7D73C; + remoteInfo = AsyncDisplayKit; + }; + D05CC2AD1B6932E900E235A3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 058D09BC195D04C000B7D73C; + remoteInfo = AsyncDisplayKitTests; + }; + D05CC2AF1B6932E900E235A3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 057D02BF1AC0A66700C7AC3C; + remoteInfo = AsyncDisplayKitTestHost; + }; + D05CC2B11B6932E900E235A3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B35061DA1B010EDF0018CF92; + remoteInfo = "AsyncDisplayKit-iOS"; + }; + D05CC32A1B697C0900E235A3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = B35061D91B010EDF0018CF92; + remoteInfo = "AsyncDisplayKit-iOS"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -29,6 +103,45 @@ D05CC26D1B69316F00E235A3 /* DisplayTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DisplayTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D05CC2721B69316F00E235A3 /* DisplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayTests.swift; sourceTree = ""; }; D05CC2741B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSignalKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-gbpsmqzuwcmmxadrqcwyrluaftwp/Build/Products/Debug-iphoneos/SwiftSignalKit.framework"; sourceTree = ""; }; + D05CC29F1B69326400E235A3 /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; + D05CC2A11B69326C00E235A3 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; + D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AsyncDisplayKit.xcodeproj; path = submodules/AsyncDisplayKit/AsyncDisplayKit.xcodeproj; sourceTree = ""; }; + D05CC2E21B69552C00E235A3 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationUtils.swift; sourceTree = ""; }; + D05CC2E51B69555800E235A3 /* CALayer+ImplicitAnimations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CALayer+ImplicitAnimations.m"; sourceTree = ""; }; + D05CC2E61B69555800E235A3 /* CALayer+ImplicitAnimations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CALayer+ImplicitAnimations.h"; sourceTree = ""; }; + D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuntimeUtils.m; sourceTree = ""; }; + D05CC2EB1B69558A00E235A3 /* RuntimeUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuntimeUtils.h; sourceTree = ""; }; + D05CC2EE1B6955D000E235A3 /* UIKitUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitUtils.swift; sourceTree = ""; }; + D05CC2EF1B6955D000E235A3 /* UIViewController+Navigation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+Navigation.m"; sourceTree = ""; }; + D05CC2F01B6955D000E235A3 /* UIViewController+Navigation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+Navigation.h"; sourceTree = ""; }; + D05CC2F11B6955D000E235A3 /* UINavigationItem+Proxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UINavigationItem+Proxy.m"; sourceTree = ""; }; + D05CC2F21B6955D000E235A3 /* UINavigationItem+Proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UINavigationItem+Proxy.h"; sourceTree = ""; }; + D05CC2F31B6955D000E235A3 /* UIKitUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIKitUtils.m; sourceTree = ""; }; + D05CC2F41B6955D000E235A3 /* UIKitUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIKitUtils.h; sourceTree = ""; }; + D05CC2F51B6955D000E235A3 /* UIWindow+OrientationChange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIWindow+OrientationChange.m"; sourceTree = ""; }; + D05CC2F61B6955D000E235A3 /* UIWindow+OrientationChange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIWindow+OrientationChange.h"; sourceTree = ""; }; + D05CC3011B69568600E235A3 /* NotificationCenterUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationCenterUtils.m; sourceTree = ""; }; + D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationCenterUtils.h; sourceTree = ""; }; + D05CC3051B69575900E235A3 /* NSBag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSBag.m; sourceTree = ""; }; + D05CC3061B69575900E235A3 /* NSBag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSBag.h; sourceTree = ""; }; + D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTransitionView.swift; sourceTree = ""; }; + D05CC30A1B695A9500E235A3 /* NavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = ""; }; + D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemWrapper.swift; sourceTree = ""; }; + D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemTransitionState.swift; sourceTree = ""; }; + D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBackButtonNode.swift; sourceTree = ""; }; + D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationButtonNode.swift; sourceTree = ""; }; + D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTitleNode.swift; sourceTree = ""; }; + D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarButtonItemWrapper.swift; sourceTree = ""; }; + D05CC3111B695A9600E235A3 /* UIBarButtonItem+Proxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBarButtonItem+Proxy.m"; sourceTree = ""; }; + D05CC3121B695A9600E235A3 /* UIBarButtonItem+Proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBarButtonItem+Proxy.h"; sourceTree = ""; }; + D05CC3131B695A9600E235A3 /* NavigationControllerProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NavigationControllerProxy.m; sourceTree = ""; }; + D05CC3141B695A9600E235A3 /* NavigationControllerProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NavigationControllerProxy.h; sourceTree = ""; }; + D05CC3221B695B0700E235A3 /* NavigationBarProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NavigationBarProxy.h; sourceTree = ""; }; + D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NavigationBarProxy.m; sourceTree = ""; }; + D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationBackArrowLight@2x.png"; sourceTree = ""; }; + D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveTransitionGestureRecognizer.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -36,6 +149,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D05CC2B61B69339A00E235A3 /* AsyncDisplayKit.framework in Frameworks */, + D05CC29A1B69323B00E235A3 /* SwiftSignalKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -53,6 +168,7 @@ D05CC2591B69316F00E235A3 = { isa = PBXGroup; children = ( + D05CC2A31B6932D500E235A3 /* Frameworks */, D05CC2651B69316F00E235A3 /* Display */, D05CC2711B69316F00E235A3 /* DisplayTests */, D05CC2641B69316F00E235A3 /* Products */, @@ -71,8 +187,11 @@ D05CC2651B69316F00E235A3 /* Display */ = { isa = PBXGroup; children = ( - D05CC2661B69316F00E235A3 /* Display.h */, - D05CC2681B69316F00E235A3 /* Info.plist */, + D05CC3001B6955D500E235A3 /* Utils */, + D05CC3211B695AA600E235A3 /* Navigation */, + D05CC2A11B69326C00E235A3 /* Window.swift */, + D05CC2E21B69552C00E235A3 /* ViewController.swift */, + D05CC2E11B69534100E235A3 /* Supporting Files */, ); path = Display; sourceTree = ""; @@ -86,6 +205,84 @@ path = DisplayTests; sourceTree = ""; }; + D05CC2A31B6932D500E235A3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */, + D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D05CC2A51B6932E800E235A3 /* Products */ = { + isa = PBXGroup; + children = ( + D05CC2AC1B6932E900E235A3 /* libAsyncDisplayKit.a */, + D05CC2AE1B6932E900E235A3 /* AsyncDisplayKitTests.xctest */, + D05CC2B01B6932E900E235A3 /* AsyncDisplayKitTestHost.app */, + D05CC2B21B6932E900E235A3 /* AsyncDisplayKit.framework */, + ); + name = Products; + sourceTree = ""; + }; + D05CC2E11B69534100E235A3 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */, + D05CC2661B69316F00E235A3 /* Display.h */, + D05CC2681B69316F00E235A3 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D05CC3001B6955D500E235A3 /* Utils */ = { + isa = PBXGroup; + children = ( + D05CC2EB1B69558A00E235A3 /* RuntimeUtils.h */, + D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */, + D05CC2F01B6955D000E235A3 /* UIViewController+Navigation.h */, + D05CC2EF1B6955D000E235A3 /* UIViewController+Navigation.m */, + D05CC2EE1B6955D000E235A3 /* UIKitUtils.swift */, + D05CC2F41B6955D000E235A3 /* UIKitUtils.h */, + D05CC2F31B6955D000E235A3 /* UIKitUtils.m */, + D05CC2F21B6955D000E235A3 /* UINavigationItem+Proxy.h */, + D05CC2F11B6955D000E235A3 /* UINavigationItem+Proxy.m */, + D05CC2F61B6955D000E235A3 /* UIWindow+OrientationChange.h */, + D05CC2F51B6955D000E235A3 /* UIWindow+OrientationChange.m */, + D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */, + D05CC3011B69568600E235A3 /* NotificationCenterUtils.m */, + D05CC2E61B69555800E235A3 /* CALayer+ImplicitAnimations.h */, + D05CC2E51B69555800E235A3 /* CALayer+ImplicitAnimations.m */, + D05CC3061B69575900E235A3 /* NSBag.h */, + D05CC3051B69575900E235A3 /* NSBag.m */, + D05CC3121B695A9600E235A3 /* UIBarButtonItem+Proxy.h */, + D05CC3111B695A9600E235A3 /* UIBarButtonItem+Proxy.m */, + D05CC3141B695A9600E235A3 /* NavigationControllerProxy.h */, + D05CC3131B695A9600E235A3 /* NavigationControllerProxy.m */, + D05CC3221B695B0700E235A3 /* NavigationBarProxy.h */, + D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */, + D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */, + ); + name = Utils; + sourceTree = ""; + }; + D05CC3211B695AA600E235A3 /* Navigation */ = { + isa = PBXGroup; + children = ( + D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */, + D05CC30A1B695A9500E235A3 /* NavigationBar.swift */, + D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */, + D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */, + D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */, + D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */, + D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */, + D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */, + D05CC29F1B69326400E235A3 /* NavigationController.swift */, + D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */, + ); + name = Navigation; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -93,7 +290,18 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + D05CC3041B69568600E235A3 /* NotificationCenterUtils.h in Headers */, + D05CC2ED1B69558A00E235A3 /* RuntimeUtils.h in Headers */, + D05CC3201B695A9600E235A3 /* NavigationControllerProxy.h in Headers */, + D05CC2E91B69555800E235A3 /* CALayer+ImplicitAnimations.h in Headers */, + D05CC2FB1B6955D000E235A3 /* UINavigationItem+Proxy.h in Headers */, + D05CC3241B695B0700E235A3 /* NavigationBarProxy.h in Headers */, + D05CC31E1B695A9600E235A3 /* UIBarButtonItem+Proxy.h in Headers */, + D05CC2FF1B6955D000E235A3 /* UIWindow+OrientationChange.h in Headers */, + D05CC2FD1B6955D000E235A3 /* UIKitUtils.h in Headers */, + D05CC3081B69575900E235A3 /* NSBag.h in Headers */, D05CC2671B69316F00E235A3 /* Display.h in Headers */, + D05CC2F91B6955D000E235A3 /* UIViewController+Navigation.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -112,6 +320,7 @@ buildRules = ( ); dependencies = ( + D05CC32B1B697C0900E235A3 /* PBXTargetDependency */, ); name = Display; productName = Display; @@ -142,6 +351,7 @@ D05CC25A1B69316F00E235A3 /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 0700; LastUpgradeCheck = 0700; ORGANIZATIONNAME = Telegram; TargetAttributes = { @@ -163,6 +373,12 @@ mainGroup = D05CC2591B69316F00E235A3; productRefGroup = D05CC2641B69316F00E235A3 /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = D05CC2A51B6932E800E235A3 /* Products */; + ProjectRef = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + }, + ); projectRoot = ""; targets = ( D05CC2621B69316F00E235A3 /* Display */, @@ -171,11 +387,43 @@ }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + D05CC2AC1B6932E900E235A3 /* libAsyncDisplayKit.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libAsyncDisplayKit.a; + remoteRef = D05CC2AB1B6932E900E235A3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + D05CC2AE1B6932E900E235A3 /* AsyncDisplayKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = AsyncDisplayKitTests.xctest; + remoteRef = D05CC2AD1B6932E900E235A3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + D05CC2B01B6932E900E235A3 /* AsyncDisplayKitTestHost.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = AsyncDisplayKitTestHost.app; + remoteRef = D05CC2AF1B6932E900E235A3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + D05CC2B21B6932E900E235A3 /* AsyncDisplayKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = AsyncDisplayKit.framework; + remoteRef = D05CC2B11B6932E900E235A3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ D05CC2611B69316F00E235A3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -193,6 +441,31 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */, + D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, + D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, + D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */, + D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */, + D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, + D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, + D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */, + D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */, + D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */, + D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */, + D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */, + D05CC3071B69575900E235A3 /* NSBag.m in Sources */, + D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */, + D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */, + D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */, + D05CC31C1B695A9600E235A3 /* BarButtonItemWrapper.swift in Sources */, + D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, + D05CC2E81B69555800E235A3 /* CALayer+ImplicitAnimations.m in Sources */, + D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, + D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, + D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, + D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */, + D05CC2A21B69326C00E235A3 /* Window.swift in Sources */, + D05CC3151B695A9600E235A3 /* NavigationTransitionView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -212,6 +485,11 @@ target = D05CC2621B69316F00E235A3 /* Display */; targetProxy = D05CC26F1B69316F00E235A3 /* PBXContainerItemProxy */; }; + D05CC32B1B697C0900E235A3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "AsyncDisplayKit-iOS"; + targetProxy = D05CC32A1B697C0900E235A3 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -307,28 +585,35 @@ D05CC2781B69316F00E235A3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; INFOPLIST_FILE = Display/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; D05CC2791B69316F00E235A3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; INFOPLIST_FILE = Display/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -375,6 +660,7 @@ D05CC2791B69316F00E235A3 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; D05CC27A1B69316F00E235A3 /* Build configuration list for PBXNativeTarget "DisplayTests" */ = { isa = XCConfigurationList; @@ -383,6 +669,7 @@ D05CC27C1B69316F00E235A3 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/Display/BarButtonItemWrapper.swift b/Display/BarButtonItemWrapper.swift new file mode 100644 index 0000000000..fab8fa1752 --- /dev/null +++ b/Display/BarButtonItemWrapper.swift @@ -0,0 +1,49 @@ +import UIKit +import AsyncDisplayKit + +internal class BarButtonItemWrapper { + let parentNode: ASDisplayNode + let barButtonItem: UIBarButtonItem + let layoutNeeded: () -> () + + let buttonNode: NavigationButtonNode + + private var setEnabledListenerKey: Int! + private var setTitleListenerKey: Int! + + init(parentNode: ASDisplayNode, barButtonItem: UIBarButtonItem, layoutNeeded: () -> ()) { + self.parentNode = parentNode + self.barButtonItem = barButtonItem + self.layoutNeeded = layoutNeeded + + self.buttonNode = NavigationButtonNode() + self.buttonNode.pressed = { [weak self] in + self?.barButtonItem.performActionOnTarget() + return + } + self.parentNode.addSubnode(self.buttonNode) + + self.setEnabledListenerKey = barButtonItem.addSetEnabledListener({ [weak self] enabled in + self?.buttonNode.enabled = enabled + return + }) + + self.setTitleListenerKey = barButtonItem.addSetTitleListener({ [weak self] title in + self?.buttonNode.text = title + if let layoutNeeded = self?.layoutNeeded { + layoutNeeded() + } + return + }) + + self.buttonNode.text = barButtonItem.title ?? "" + self.buttonNode.enabled = barButtonItem.enabled ?? true + self.buttonNode.bold = (barButtonItem.style ?? UIBarButtonItemStyle.Plain) == UIBarButtonItemStyle.Done + } + + deinit { + self.barButtonItem.removeSetTitleListener(self.setTitleListenerKey) + self.barButtonItem.removeSetEnabledListener(self.setEnabledListenerKey) + self.buttonNode.removeFromSupernode() + } +} diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift new file mode 100644 index 0000000000..6e5efec0fa --- /dev/null +++ b/Display/CAAnimationUtils.swift @@ -0,0 +1,32 @@ +import UIKit + +extension CALayer { + internal func animate(from from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: NSTimeInterval) { + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + let animation = CABasicAnimation(keyPath: keyPath) + animation.fromValue = from + animation.toValue = to + animation.duration = duration + animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + animation.removedOnCompletion = true + animation.fillMode = kCAFillModeForwards + animation.speed = speed + + self.addAnimation(animation, forKey: keyPath) + + self.setValue(to, forKey: keyPath) + } + + internal func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { + self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration) + } + + internal func animatePosition(from from: CGPoint, to: CGPoint, duration: NSTimeInterval) { + self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration) + } +} diff --git a/Display/CALayer+ImplicitAnimations.h b/Display/CALayer+ImplicitAnimations.h new file mode 100644 index 0000000000..541efc951b --- /dev/null +++ b/Display/CALayer+ImplicitAnimations.h @@ -0,0 +1,20 @@ +#import + +@interface CALayer (ImplicitAnimations) + ++ (void)beginRecordingChanges; ++ (NSArray *)endRecordingChanges; + +@end + +@interface CALayerAnimation : NSObject + +@property (nonatomic, weak, readonly) CALayer *layer; + +@property (nonatomic, readonly) CGRect startBounds; +@property (nonatomic, readonly) CGRect endBounds; + +@property (nonatomic, readonly) CGPoint startPosition; +@property (nonatomic, readonly) CGPoint endPosition; + +@end diff --git a/Display/CALayer+ImplicitAnimations.m b/Display/CALayer+ImplicitAnimations.m new file mode 100644 index 0000000000..2229d1694f --- /dev/null +++ b/Display/CALayer+ImplicitAnimations.m @@ -0,0 +1,142 @@ +#import "CALayer+ImplicitAnimations.h" + +#import + +#import "RuntimeUtils.h" +#import + +static bool recordingChanges = false; +static NSMutableArray *currentLayerAnimations() +{ + static NSMutableArray *array = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + array = [[NSMutableArray alloc] init]; + }); + + return array; +} + +@implementation CALayerAnimation + +- (instancetype)initWithLayer:(CALayer *)layer +{ + self = [super init]; + if (self != nil) + { + _layer = layer; + + _startBounds = layer.bounds; + _startPosition = layer.position; + + _endBounds = _startBounds; + _endPosition = _startPosition; + } + return self; +} + +- (void)setEndBounds:(CGRect)endBounds +{ + _endBounds = endBounds; +} + +- (void)setEndPosition:(CGPoint)endPosition +{ + _endPosition = endPosition; +} + +@end + +@interface CALayer (_ca836a62_) + +@end + +@implementation CALayer (_ca836a62_) + +- (void)_ca836a62_setBounds:(CGRect)bounds +{ + if (recordingChanges && [self.delegate isKindOfClass:[ASDisplayNode class]]) + { + CALayerAnimation *animation = nil; + for (CALayerAnimation *listAnimation in currentLayerAnimations()) + { + if (listAnimation.layer == self) + { + animation = listAnimation; + break; + } + } + if (animation == nil) + { + animation = [[CALayerAnimation alloc] initWithLayer:self]; + [currentLayerAnimations() addObject:animation]; + } + [animation setEndBounds:bounds]; + } + + [self _ca836a62_setBounds:bounds]; +} + +- (void)_ca836a62_setPosition:(CGPoint)position +{ + if (recordingChanges && [self.delegate isKindOfClass:[ASDisplayNode class]]) + { + CALayerAnimation *animation = nil; + for (CALayerAnimation *listAnimation in currentLayerAnimations()) + { + if (listAnimation.layer == self) + { + animation = listAnimation; + break; + } + } + if (animation == nil) + { + animation = [[CALayerAnimation alloc] initWithLayer:self]; + [currentLayerAnimations() addObject:animation]; + } + [animation setEndPosition:position]; + } + + [self _ca836a62_setPosition:position]; +} + +@end + +@interface LayerAnimationExtensions : NSObject + +@end + +@implementation LayerAnimationExtensions + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setBounds:) newSelector:@selector(_ca836a62_setBounds:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setPosition:) newSelector:@selector(_ca836a62_setPosition:)]; + }); +} + +@end + +@implementation CALayer (ImplicitAnimations) + ++ (void)beginRecordingChanges +{ + recordingChanges = true; + [currentLayerAnimations() removeAllObjects]; +} + ++ (NSArray *)endRecordingChanges +{ + recordingChanges = false; + NSArray *array = [[NSArray alloc] initWithArray:currentLayerAnimations()]; + [currentLayerAnimations() removeAllObjects]; + + return array; +} + +@end diff --git a/Display/Display.h b/Display/Display.h index 94f35b8246..8cb75256da 100644 --- a/Display/Display.h +++ b/Display/Display.h @@ -16,4 +16,15 @@ FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import - +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/Display/InteractiveTransitionGestureRecognizer.swift b/Display/InteractiveTransitionGestureRecognizer.swift new file mode 100644 index 0000000000..107d85bdf6 --- /dev/null +++ b/Display/InteractiveTransitionGestureRecognizer.swift @@ -0,0 +1,44 @@ +import Foundation +import UIKit + +class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { + var validatedGesture = false + var firstLocation: CGPoint = CGPoint() + + override init(target: AnyObject?, action: Selector) { + super.init(target: target, action: action) + + self.maximumNumberOfTouches = 1 + } + + override func reset() { + super.reset() + + validatedGesture = false + } + + override func touchesBegan(touches: Set, withEvent event: UIEvent) { + super.touchesBegan(touches, withEvent: event) + + self.firstLocation = touches.first!.locationInView(self.view) + } + + override func touchesMoved(touches: Set, withEvent event: UIEvent) { + let location = touches.first!.locationInView(self.view) + let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y) + + if !validatedGesture { + if translation.x < 0.0 { + self.state = .Failed + } else if abs(translation.y) >= 2.0 { + self.state = .Failed + } else if translation.x >= 3.0 && translation.x / 3.0 > translation.y { + validatedGesture = true + } + } + + if validatedGesture { + super.touchesMoved(touches, withEvent: event) + } + } +} diff --git a/Display/NSBag.h b/Display/NSBag.h new file mode 100644 index 0000000000..739fe92552 --- /dev/null +++ b/Display/NSBag.h @@ -0,0 +1,9 @@ +#import + +@interface NSBag : NSObject + +- (NSInteger)addItem:(id)item; +- (void)enumerateItems:(void (^)(id))block; +- (void)removeItem:(NSInteger)key; + +@end diff --git a/Display/NSBag.m b/Display/NSBag.m new file mode 100644 index 0000000000..4da04f5737 --- /dev/null +++ b/Display/NSBag.m @@ -0,0 +1,64 @@ +#import "NSBag.h" + +@interface NSBag () +{ + NSInteger _nextKey; + NSMutableArray *_items; + NSMutableArray *_itemKeys; +} + +@end + +@implementation NSBag + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + _items = [[NSMutableArray alloc] init]; + _itemKeys = [[NSMutableArray alloc] init]; + } + return self; +} + +- (NSInteger)addItem:(id)item +{ + if (item == nil) + return -1; + + NSInteger key = _nextKey; + [_items addObject:item]; + [_itemKeys addObject:@(key)]; + _nextKey++; + + return key; +} + +- (void)enumerateItems:(void (^)(id))block +{ + if (block) + { + for (id item in _items) + { + block(item); + } + } +} + +- (void)removeItem:(NSInteger)key +{ + NSUInteger index = 0; + for (NSNumber *itemKey in _itemKeys) + { + if ([itemKey integerValue] == key) + { + [_items removeObjectAtIndex:index]; + [_itemKeys removeObjectAtIndex:index]; + break; + } + index++; + } +} + +@end diff --git a/Display/NavigationBackArrowLight@2x.png b/Display/NavigationBackArrowLight@2x.png new file mode 100644 index 0000000000..ca8cabf210 Binary files /dev/null and b/Display/NavigationBackArrowLight@2x.png differ diff --git a/Display/NavigationBackButtonNode.swift b/Display/NavigationBackButtonNode.swift new file mode 100644 index 0000000000..6f483e09e1 --- /dev/null +++ b/Display/NavigationBackButtonNode.swift @@ -0,0 +1,143 @@ +import UIKit +import AsyncDisplayKit + +public class NavigationBackButtonNode: ASControlNode { + private func fontForCurrentState() -> UIFont { + return UIFont.systemFontOfSize(17.0) + } + + private func attributesForCurrentState() -> [String : AnyObject] { + return [ + NSFontAttributeName: self.fontForCurrentState(), + NSForegroundColorAttributeName: self.enabled ? UIColor.blueColor() : UIColor.grayColor() + ] + } + + var suspendLayout = false + + let arrow: ASDisplayNode + let label: ASTextNode + + private let arrowSpacing: CGFloat = 4.0 + + private var _text: String = "" + var text: String { + get { + return self._text + } + set(value) { + self._text = value + self.label.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.invalidateCalculatedLayout() + } + } + + private var touchCount = 0 + var pressed: () -> () = {} + + override init() { + self.arrow = ASDisplayNode() + self.label = ASTextNode() + + super.init() + + self.userInteractionEnabled = true + self.exclusiveTouch = true + self.hitTestSlop = UIEdgeInsets(top: -16.0, left: -10.0, bottom: -16.0, right: -10.0) + self.displaysAsynchronously = false + + self.arrow.displaysAsynchronously = false + self.label.displaysAsynchronously = false + + self.addSubnode(self.arrow) + let arrowImage = UIImage(named: "NavigationBackArrowLight", inBundle: NSBundle(forClass: NavigationBackButtonNode.self), compatibleWithTraitCollection: nil) + self.arrow.contents = arrowImage?.CGImage + self.arrow.frame = CGRect(origin: CGPoint(), size: arrowImage?.size ?? CGSize()) + + self.addSubnode(self.label) + } + + public override func calculateSizeThatFits(constrainedSize: CGSize) -> CGSize { + self.label.measure(CGSize(width: max(0.0, constrainedSize.width - self.arrow.frame.size.width - self.arrowSpacing), height: constrainedSize.height)) + + return CGSize(width: self.arrow.frame.size.width + self.arrowSpacing + self.label.calculatedSize.width, height: max(self.arrow.frame.size.height, self.label.calculatedSize.height)) + } + + var labelFrame: CGRect { + get { + return CGRect(x: self.arrow.frame.size.width + self.arrowSpacing, y: floor((self.frame.size.height - self.label.calculatedSize.height) / 2.0), width: self.label.calculatedSize.width, height: self.label.calculatedSize.height) + } + } + + public override func layout() { + super.layout() + + if self.suspendLayout { + return + } + + self.arrow.frame = CGRect(x: 0.0, y: floor((self.frame.size.height - arrow.frame.size.height) / 2.0), width: self.arrow.frame.size.width, height: self.arrow.frame.size.height) + + self.label.frame = self.labelFrame + } + + private func touchInsideApparentBounds(touch: UITouch) -> Bool { + var apparentBounds = self.bounds + let hitTestSlop = self.hitTestSlop + apparentBounds.origin.x += hitTestSlop.left + apparentBounds.size.width -= hitTestSlop.left + hitTestSlop.right + apparentBounds.origin.y += hitTestSlop.top + apparentBounds.size.height -= hitTestSlop.top + hitTestSlop.bottom + + return CGRectContainsPoint(apparentBounds, touch.locationInView(self.view)) + } + + public override func touchesBegan(touches: Set!, withEvent event: UIEvent!) { + super.touchesBegan(touches, withEvent: event) + self.touchCount += touches.count + self.updateHighlightedState(true, animated: false) + } + + public override func touchesMoved(touches: Set!, withEvent event: UIEvent!) { + super.touchesMoved(touches, withEvent: event) + + self.updateHighlightedState(self.touchInsideApparentBounds(touches.first as! UITouch), animated: true) + } + + public override func touchesEnded(touches: Set!, withEvent event: UIEvent!) { + super.touchesEnded(touches, withEvent: event) + self.updateHighlightedState(false, animated: false) + + let previousTouchCount = self.touchCount + self.touchCount = max(0, self.touchCount - touches.count) + + if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first as! UITouch) { + self.pressed() + } + } + + public override func touchesCancelled(touches: Set!, withEvent event: UIEvent!) { + super.touchesCancelled(touches, withEvent: event) + + self.touchCount = max(0, self.touchCount - touches.count) + self.updateHighlightedState(false, animated: false) + } + + private var _highlighted = false + private func updateHighlightedState(highlighted: Bool, animated: Bool) { + if _highlighted != highlighted { + _highlighted = highlighted + + let alpha: CGFloat = !enabled ? 1.0 : (highlighted ? 0.4 : 1.0) + + if animated { + UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: { () -> Void in + self.alpha = alpha + }, completion: nil) + } + else { + self.alpha = alpha + } + } + } +} diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift new file mode 100644 index 0000000000..186ef84e77 --- /dev/null +++ b/Display/NavigationBar.swift @@ -0,0 +1,120 @@ +import UIKit +import AsyncDisplayKit + +private enum ItemAnimation { + case None + case Push + case Pop +} + +public class NavigationBar: ASDisplayNode { + private var topItem: UINavigationItem? + private var topItemWrapper: NavigationItemWrapper? + + private var tempItem: UINavigationItem? + private var tempItemWrapper: NavigationItemWrapper? + + var backPressed: () -> () = { } + + private var collapsed: Bool { + get { + return self.frame.size.height < (20.0 + 44.0) + } + } + + var _proxy: NavigationBarProxy? + var proxy: NavigationBarProxy? { + get { + return self._proxy + } + set(value) { + self._proxy = value + self._proxy?.setItemsProxy = {[weak self] previousItems, items, animated in + if let strongSelf = self { + var animation = ItemAnimation.None + if animated && previousItems.count != 0 && items.count != 0 { + if previousItems.filter({element in element === items[items.count - 1]}).count != 0 { + animation = .Pop + } + else { + animation = .Push + } + } + + let count = items.count + if count != 0 { + strongSelf.updateTopItem(items[count - 1] as! UINavigationItem, previousItem: count >= 2 ? (items[count - 2] as! UINavigationItem) : nil, animation: animation) + } + } + return + } + } + } + let stripeView: UIView + + public override init() { + stripeView = UIView() + stripeView.backgroundColor = UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0) + + super.init() + + self.backgroundColor = UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0) + + self.view.addSubview(stripeView) + } + + private func updateTopItem(item: UINavigationItem, previousItem: UINavigationItem?, animation: ItemAnimation) { + if self.topItem !== item { + let previousTopItemWrapper = self.topItemWrapper + self.topItemWrapper = nil + + self.topItem = item + self.topItemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: item, previousNavigationItem: previousItem) + self.topItemWrapper?.backPressed = { [weak self] in + if let backPressed = self?.backPressed { + backPressed() + } + } + + self.topItemWrapper?.layoutItems() + + switch animation { + case .None: + break + case .Push: + self.topItemWrapper?.animatePush(previousTopItemWrapper, duration: 0.3) + break + case .Pop: + self.topItemWrapper?.animatePop(previousTopItemWrapper, duration: 0.3) + break + } + } + } + + public func beginInteractivePopProgress(previousItem: UINavigationItem, evenMorePreviousItem: UINavigationItem?) { + self.tempItem = previousItem + self.tempItemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: previousItem, previousNavigationItem: evenMorePreviousItem) + + self.tempItemWrapper?.layoutItems() + + self.setInteractivePopProgress(0.0) + } + + public func endInteractivePopProgress() { + self.tempItem = nil + self.tempItemWrapper = nil + } + + public func setInteractivePopProgress(progress: CGFloat) { + if let topItemWrapper = self.topItemWrapper { + self.tempItemWrapper?.setInteractivePopProgress(progress, previousItemWrapper: topItemWrapper) + } + } + + public override func layout() { + self.stripeView.frame = CGRect(x: 0.0, y: self.frame.size.height - 0.5, width: self.frame.size.width, height: 0.5) + + self.topItemWrapper?.layoutItems() + self.tempItemWrapper?.layoutItems() + } +} diff --git a/Display/NavigationBarProxy.h b/Display/NavigationBarProxy.h new file mode 100644 index 0000000000..9936b4726a --- /dev/null +++ b/Display/NavigationBarProxy.h @@ -0,0 +1,7 @@ +#import + +@interface NavigationBarProxy : UINavigationBar + +@property (nonatomic, copy) void (^setItemsProxy)(NSArray *, NSArray *, bool); + +@end diff --git a/Display/NavigationBarProxy.m b/Display/NavigationBarProxy.m new file mode 100644 index 0000000000..9f5700c13b --- /dev/null +++ b/Display/NavigationBarProxy.m @@ -0,0 +1,67 @@ +#import "NavigationBarProxy.h" + +@interface NavigationBarProxy () +{ + NSArray *_items; +} + +@end + +@implementation NavigationBarProxy + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self != nil) + { + } + return self; +} + +- (void)pushNavigationItem:(UINavigationItem *)item animated:(BOOL)animated +{ + [self setItems:[[self items] arrayByAddingObject:item] animated:animated]; +} + +- (UINavigationItem *)popNavigationItemAnimated:(BOOL)animated +{ + NSMutableArray *items = [[NSMutableArray alloc] initWithArray:[self items]]; + UINavigationItem *lastItem = [items lastObject]; + [items removeLastObject]; + [self setItems:items animated:animated]; + return lastItem; +} + +- (UINavigationItem *)topItem +{ + return [[self items] lastObject]; +} + +- (UINavigationItem *)backItem +{ + NSLog(@"backItem"); + return nil; +} + +- (NSArray *)items +{ + if (_items == nil) + return @[]; + return _items; +} + +- (void)setItems:(NSArray *)items +{ + [self setItems:items animated:false]; +} + +- (void)setItems:(NSArray *)items animated:(BOOL)animated +{ + NSArray *previousItems = _items; + _items = items; + + if (_setItemsProxy) + _setItemsProxy(previousItems, items, animated); +} + +@end diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift new file mode 100644 index 0000000000..80c63b657a --- /dev/null +++ b/Display/NavigationButtonNode.swift @@ -0,0 +1,126 @@ +import UIKit +import AsyncDisplayKit + +public class NavigationButtonNode: ASTextNode { + private func fontForCurrentState() -> UIFont { + return self.bold ? UIFont.boldSystemFontOfSize(17.0) : UIFont.systemFontOfSize(17.0) + } + + private func attributesForCurrentState() -> [String : AnyObject] { + return [ + NSFontAttributeName: self.fontForCurrentState(), + NSForegroundColorAttributeName: self.enabled ? UIColor.blueColor() : UIColor.grayColor() + ] + } + + private var _text: String? + public var text: String { + get { + return _text ?? "" + } + set(value) { + _text = value + + self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + + private var _bold: Bool = false + public var bold: Bool { + get { + return _bold + } + set(value) { + if _bold != value { + _bold = value + + self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + } + + private var touchCount = 0 + public var pressed: () -> () = {} + + public override init() { + super.init() + + self.userInteractionEnabled = true + self.exclusiveTouch = true + self.hitTestSlop = UIEdgeInsets(top: -16.0, left: -10.0, bottom: -16.0, right: -10.0) + self.displaysAsynchronously = false + } + + private func touchInsideApparentBounds(touch: UITouch) -> Bool { + var apparentBounds = self.bounds + let hitTestSlop = self.hitTestSlop + apparentBounds.origin.x += hitTestSlop.left + apparentBounds.size.width -= hitTestSlop.left + hitTestSlop.right + apparentBounds.origin.y += hitTestSlop.top + apparentBounds.size.height -= hitTestSlop.top + hitTestSlop.bottom + + return CGRectContainsPoint(apparentBounds, touch.locationInView(self.view)) + } + + public override func touchesBegan(touches: Set!, withEvent event: UIEvent!) { + super.touchesBegan(touches, withEvent: event) + self.touchCount += touches.count + self.updateHighlightedState(true, animated: false) + } + + public override func touchesMoved(touches: Set!, withEvent event: UIEvent!) { + super.touchesMoved(touches, withEvent: event) + + self.updateHighlightedState(self.touchInsideApparentBounds(touches.first as! UITouch), animated: true) + } + + public override func touchesEnded(touches: Set!, withEvent event: UIEvent!) { + super.touchesEnded(touches, withEvent: event) + self.updateHighlightedState(false, animated: false) + + let previousTouchCount = self.touchCount + self.touchCount = max(0, self.touchCount - touches.count) + + if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first as! UITouch) { + self.pressed() + } + } + + public override func touchesCancelled(touches: Set!, withEvent event: UIEvent!) { + super.touchesCancelled(touches, withEvent: event) + + self.touchCount = max(0, self.touchCount - touches.count) + self.updateHighlightedState(false, animated: false) + } + + private var _highlighted = false + private func updateHighlightedState(highlighted: Bool, animated: Bool) { + if _highlighted != highlighted { + _highlighted = highlighted + + let alpha: CGFloat = !enabled ? 1.0 : (highlighted ? 0.4 : 1.0) + + if animated { + UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: { () -> Void in + self.alpha = alpha + }, completion: nil) + } + else { + self.alpha = alpha + } + } + } + + public override var enabled: Bool { + get { + return super.enabled + } + set(value) { + if self.enabled != value { + super.enabled = value + + self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + } +} diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift new file mode 100644 index 0000000000..a8d88014ba --- /dev/null +++ b/Display/NavigationController.swift @@ -0,0 +1,232 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +public class NavigationController: NavigationControllerProxy, WindowContentController, UIGestureRecognizerDelegate { + private var _navigationBar: NavigationBar? + private var navigationTransitionCoordinator: NavigationTransitionCoordinator? + + public override init() { + super.init() + self._navigationBar = NavigationBar() + self._navigationBar?.frame = CGRect(x: 0.0, y: 0.0, width: 320.0, height: 44.0) + self._navigationBar?.proxy = self.navigationBar as? NavigationBarProxy + self._navigationBar?.backPressed = { [weak self] in + if self?.viewControllers.count > 1 { + self?.popViewControllerAnimated(true) + } + return + } + } + + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func loadView() { + super.loadView() + + if let _navigationBar = self._navigationBar { + self.navigationBar.superview?.insertSubview(_navigationBar.view, aboveSubview: self.navigationBar) + } + self.navigationBar.removeFromSuperview() + + self._navigationBar?.frame = navigationBarFrame(self.view.frame.size) + + let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: Selector("panGesture:")) + panRecognizer.delegate = self + panRecognizer.cancelsTouchesInView = true + self.view.addGestureRecognizer(panRecognizer) + + if self.topViewController != nil { + self.topViewController?.view.frame = CGRect(origin: CGPoint(), size: self.view.frame.size) + } + } + + func panGesture(recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case UIGestureRecognizerState.Began: + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.viewWillDisappear(true) + let topView = topController.view + bottomController.viewWillAppear(true) + let bottomView = bottomController.view + + let navigationTransitionCoordinator = NavigationTransitionCoordinator(container: self.view, topView: topView, bottomView: bottomView, navigationBar: self._navigationBar!) + self.navigationTransitionCoordinator = navigationTransitionCoordinator + + self._navigationBar?.beginInteractivePopProgress(bottomController.navigationItem, evenMorePreviousItem: self.viewControllers.count >= 3 ? (self.viewControllers[self.viewControllers.count - 3] as UIViewController).navigationItem : nil) + } + case UIGestureRecognizerState.Changed: + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + let translation = recognizer.translationInView(self.view).x + navigationTransitionCoordinator.progress = max(0.0, min(1.0, translation / self.view.frame.width)) + } + case UIGestureRecognizerState.Ended: + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + let velocity = recognizer.velocityInView(self.view).x + + if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 { + navigationTransitionCoordinator.animateCompletion(velocity, completion: { + self.navigationTransitionCoordinator = nil + + self._navigationBar?.endInteractivePopProgress() + + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.setIgnoreAppearanceMethodInvocations(true) + bottomController.setIgnoreAppearanceMethodInvocations(true) + self.popViewControllerAnimated(false) + topController.setIgnoreAppearanceMethodInvocations(false) + bottomController.setIgnoreAppearanceMethodInvocations(false) + + topController.viewDidDisappear(true) + bottomController.viewDidAppear(true) + } + }) + } + else { + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.viewWillAppear(true) + bottomController.viewWillDisappear(true) + } + + navigationTransitionCoordinator.animateCancel({ + self.navigationTransitionCoordinator = nil + + self._navigationBar?.endInteractivePopProgress() + + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.viewDidAppear(true) + bottomController.viewDidDisappear(true) + } + }) + } + } + case .Cancelled: + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.viewWillAppear(true) + bottomController.viewWillDisappear(true) + } + + navigationTransitionCoordinator.animateCancel({ + self.navigationTransitionCoordinator = nil + + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.viewDidAppear(true) + bottomController.viewDidDisappear(true) + } + }) + } + default: + break + } + } + + public override func pushViewController(viewController: UIViewController, animated: Bool) { + var controllers = self.viewControllers + controllers.append(viewController) + self.setViewControllers(controllers, animated: animated) + } + + public override func popViewControllerAnimated(animated: Bool) -> UIViewController? { + var controller: UIViewController? + var controllers = self.viewControllers + if controllers.count != 0 { + controller = controllers[controllers.count - 1] as UIViewController + controllers.removeAtIndex(controllers.count - 1) + self.setViewControllers(controllers, animated: animated) + } + return controller + } + + public override func setViewControllers(viewControllers: [UIViewController], animated: Bool) { + if viewControllers.count > 0 { + let topViewController = viewControllers[viewControllers.count - 1] as UIViewController + + if let controller = topViewController as? WindowContentController { + controller.setViewSize(self.view.bounds.size, duration: 0.0) + } else { + topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size) + } + } + + super.setViewControllers(viewControllers, animated: animated) + } + + private func navigationBarFrame(size: CGSize) -> CGRect { + let condensedBar = (size.height < size.width || size.height <= 320.0) && size.height < 768.0 + return CGRect(x: 0.0, y: 0.0, width: size.width, height: 20.0 + (size.height >= size.width ? 44.0 : 32.0)) + } + + public func setViewSize(toSize: CGSize, duration: NSTimeInterval) { + if duration > DBL_EPSILON { + animateRotation(self.view, toFrame: CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height), duration: duration) + } + else { + self.view.frame = CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height) + } + + if duration > DBL_EPSILON { + animateRotation(self._navigationBar, toFrame: self.navigationBarFrame(toSize), duration: duration) + } + else { + self._navigationBar?.frame = self.navigationBarFrame(toSize) + } + + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + //navigationTransitionView.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) + + if self.viewControllers.count >= 2 { + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + if let controller = bottomController as? WindowContentController { + controller.setViewSize(toSize, duration: duration) + } + bottomController.view.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) + } + } + + if let topViewController = self.topViewController { + if let controller = topViewController as? WindowContentController { + controller.setViewSize(toSize, duration: duration) + } else { + topViewController.view.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) + } + } + + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + navigationTransitionCoordinator.updateProgress() + } + } + + public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } + + public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } +} diff --git a/Display/NavigationControllerProxy.h b/Display/NavigationControllerProxy.h new file mode 100644 index 0000000000..02e4fb6208 --- /dev/null +++ b/Display/NavigationControllerProxy.h @@ -0,0 +1,7 @@ +#import + +@interface NavigationControllerProxy : UINavigationController + +- (instancetype)init; + +@end diff --git a/Display/NavigationControllerProxy.m b/Display/NavigationControllerProxy.m new file mode 100644 index 0000000000..a6e77f8a55 --- /dev/null +++ b/Display/NavigationControllerProxy.m @@ -0,0 +1,16 @@ +#import "NavigationControllerProxy.h" + +#import "NavigationBarProxy.h" + +@implementation NavigationControllerProxy + +- (instancetype)init +{ + self = [super initWithNavigationBarClass:[NavigationBarProxy class] toolbarClass:[UIToolbar class]]; + if (self != nil) + { + } + return self; +} + +@end diff --git a/Display/NavigationItemTransitionState.swift b/Display/NavigationItemTransitionState.swift new file mode 100644 index 0000000000..179e9a3821 --- /dev/null +++ b/Display/NavigationItemTransitionState.swift @@ -0,0 +1,4 @@ +struct NavigationItemTransitionState { + let backButtonPosition: CGPoint? + let titlePosition: CGPoint +} diff --git a/Display/NavigationItemWrapper.swift b/Display/NavigationItemWrapper.swift new file mode 100644 index 0000000000..0ae268cc07 --- /dev/null +++ b/Display/NavigationItemWrapper.swift @@ -0,0 +1,332 @@ +import UIKit +import AsyncDisplayKit + +internal class NavigationItemWrapper { + let parentNode: ASDisplayNode + + private var navigationItem: UINavigationItem + private var setTitleListenerKey: Int! + private var setLeftBarButtonItemListenerKey: Int! + private var setRightBarButtonItemListenerKey: Int! + + private var previousNavigationItem: UINavigationItem? + private var previousItemSetTitleListenerKey: Int? + + private let titleNode: NavigationTitleNode + private var backButtonNode: NavigationBackButtonNode + private var leftBarButtonItem: UIBarButtonItem? + private var leftBarButtonItemWrapper: BarButtonItemWrapper? + private var rightBarButtonItem: UIBarButtonItem? + private var rightBarButtonItemWrapper: BarButtonItemWrapper? + + var backPressed: () -> () = { } + + var suspendLayout = false + + init(parentNode: ASDisplayNode, navigationItem: UINavigationItem, previousNavigationItem: UINavigationItem?) { + self.parentNode = parentNode + self.navigationItem = navigationItem + self.previousNavigationItem = previousNavigationItem + + self.titleNode = NavigationTitleNode(text: "") + self.parentNode.addSubnode(titleNode) + + self.backButtonNode = NavigationBackButtonNode() + backButtonNode.pressed = { [weak self] in + if let backPressed = self?.backPressed { + backPressed() + } + } + self.parentNode.addSubnode(self.backButtonNode) + + self.previousItemSetTitleListenerKey = previousNavigationItem?.addSetTitleListener({ [weak self] title in + self?.setBackButtonTitle(title) + return + }) + + self.setTitleListenerKey = navigationItem.addSetTitleListener({ [weak self] title in + self?.setTitle(title) + return + }) + + self.setLeftBarButtonItemListenerKey = navigationItem.addSetLeftBarButtonItemListener({ [weak self] barButtonItem, animated in + self?.setLeftBarButtonItem(barButtonItem, animated: animated) + return + }) + + self.setRightBarButtonItemListenerKey = navigationItem.addSetRightBarButtonItemListener({ [weak self] barButtonItem, animated in + self?.setRightBarButtonItem(barButtonItem, animated: animated) + return + }) + + self.setTitle(navigationItem.title ?? "") + self.setBackButtonTitle(previousNavigationItem?.title ?? "Back") + self.setLeftBarButtonItem(navigationItem.leftBarButtonItem, animated: false) + self.setRightBarButtonItem(navigationItem.rightBarButtonItem, animated: false) + } + + deinit { + self.navigationItem.removeSetTitleListener(self.setTitleListenerKey) + self.navigationItem.removeSetLeftBarButtonItemListener(self.setLeftBarButtonItemListenerKey) + self.navigationItem.removeSetRightBarButtonItemListener(self.setRightBarButtonItemListenerKey) + + if let previousItemSetTitleListenerKey = self.previousItemSetTitleListenerKey { + self.previousNavigationItem?.removeSetTitleListener(previousItemSetTitleListenerKey) + } + + self.titleNode.removeFromSupernode() + self.backButtonNode.removeFromSupernode() + } + + func setBackButtonTitle(backButtonTitle: String) { + self.backButtonNode.text = backButtonTitle + self.layoutItems() + } + + func setTitle(title: String) { + self.titleNode.text = title + self.layoutItems() + } + + func setLeftBarButtonItem(leftBarButtonItem: UIBarButtonItem?, animated: Bool) { + if self.leftBarButtonItem !== leftBarButtonItem { + self.leftBarButtonItem = leftBarButtonItem + + self.leftBarButtonItemWrapper = nil + + if let leftBarButtonItem = leftBarButtonItem { + self.leftBarButtonItemWrapper = BarButtonItemWrapper(parentNode: self.parentNode, barButtonItem: leftBarButtonItem, layoutNeeded: { [weak self] in + self?.layoutItems() + return + }) + } + } + + self.backButtonNode.hidden = self.previousNavigationItem == nil || self.leftBarButtonItemWrapper != nil + } + + func setRightBarButtonItem(rightBarButtonItem: UIBarButtonItem?, animated: Bool) { + if self.rightBarButtonItem !== rightBarButtonItem { + self.rightBarButtonItem = rightBarButtonItem + + self.rightBarButtonItemWrapper = nil + + if let rightBarButtonItem = rightBarButtonItem { + self.rightBarButtonItemWrapper = BarButtonItemWrapper(parentNode: self.parentNode, barButtonItem: rightBarButtonItem, layoutNeeded: { [weak self] in + self?.layoutItems() + return + }) + } + } + } + + private var collapsed: Bool { + get { + return self.parentNode.frame.size.height < (20.0 + 44.0) + } + } + + var titleFrame: CGRect { + get { + return CGRect(x: floor((self.parentNode.frame.size.width - self.titleNode.calculatedSize.width) / 2.0), y: self.collapsed ? 24.0 : 31.0, width: self.titleNode.calculatedSize.width, height: self.titleNode.calculatedSize.height) + } + } + + var titlePosition: CGPoint { + get { + let titleFrame = self.titleFrame + return CGPoint(x: CGRectGetMidX(titleFrame), y: CGRectGetMidY(titleFrame)) + } + } + + var backButtonFrame: CGRect { + get { + return CGRect(x: self.collapsed ? 15.0 : 8.0, y: self.collapsed ? 24.0 : 31.0, width: backButtonNode.calculatedSize.width, height: backButtonNode.calculatedSize.height) + } + } + + var backButtonLabelFrame: CGRect { + get { + let backButtonFrame = self.backButtonFrame + let labelFrame = self.backButtonNode.labelFrame + return CGRect(origin: CGPoint(x: backButtonFrame.origin.x + labelFrame.origin.x, y: backButtonFrame.origin.y + labelFrame.origin.y), size: labelFrame.size) + } + } + + var backButtonLabelPosition: CGPoint { + get { + let backButtonLabelFrame = self.backButtonLabelFrame + return CGPoint(x: CGRectGetMidX(backButtonLabelFrame), y: CGRectGetMidY(backButtonLabelFrame)) + } + } + + var leftButtonFrame: CGRect? { + get { + if let leftBarButtonItemWrapper = self.leftBarButtonItemWrapper { + return CGRect(x: self.collapsed ? 15.0 : 8.0, y: self.collapsed ? 24.0 : 31.0, width: leftBarButtonItemWrapper.buttonNode.calculatedSize.width, height: leftBarButtonItemWrapper.buttonNode.calculatedSize.height) + } + else { + return nil + } + } + } + + var rightButtonFrame: CGRect? { + get { + if let rightBarButtonItemWrapper = self.rightBarButtonItemWrapper { + return CGRect(x: self.parentNode.frame.size.width - rightBarButtonItemWrapper.buttonNode.calculatedSize.width - (self.collapsed ? 15.0 : 8.0), y: self.collapsed ? 24.0 : 31.0, width: rightBarButtonItemWrapper.buttonNode.calculatedSize.width, height: rightBarButtonItemWrapper.buttonNode.calculatedSize.height) + } + else { + return nil + } + } + } + + var transitionState: NavigationItemTransitionState { + get { + return NavigationItemTransitionState(backButtonPosition: self.backButtonNode.hidden ? nil : self.backButtonLabelPosition, titlePosition: self.titlePosition) + } + } + + func layoutItems() { + if suspendLayout { + return + } + self.titleNode.measure(self.parentNode.bounds.size) + self.titleNode.frame = self.titleFrame + + self.backButtonNode.measure(self.parentNode.frame.size) + self.backButtonNode.frame = self.backButtonFrame + self.backButtonNode.layout() + + if let leftBarButtonItemWrapper = self.leftBarButtonItemWrapper { + leftBarButtonItemWrapper.buttonNode.measure(self.parentNode.frame.size) + leftBarButtonItemWrapper.buttonNode.frame = self.leftButtonFrame! + } + + if let rightBarButtonItemWrapper = self.rightBarButtonItemWrapper { + rightBarButtonItemWrapper.buttonNode.measure(self.parentNode.frame.size) + rightBarButtonItemWrapper.buttonNode.frame = self.rightButtonFrame! + } + } + + func interpolatePosition(from: CGPoint, _ to: CGPoint, value: CGFloat) -> CGPoint { + return CGPoint(x: from.x * (CGFloat(1.0) - value) + to.x * value, y: from.y * (CGFloat(1.0) - value) + to.y * value) + } + + func interpolateValue(from: CGFloat, _ to: CGFloat, value: CGFloat) -> CGFloat { + return (from * (CGFloat(1.0) - value)) + (to * value) + } + + func applyPushAnimationProgress(previousItemState previousItemState: NavigationItemTransitionState, value: CGFloat) { + let titleStartPosition = CGPoint(x: self.parentNode.frame.size.width + self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) + let titleStartAlpha: CGFloat = 0.0 + let titleEndPosition = self.titlePosition + let titleEndAlpha: CGFloat = 1.0 + self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) + self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) + + self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) + self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) + + self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: previousItemState.titlePosition.x - self.backButtonFrame.origin.x, y: previousItemState.titlePosition.y - self.backButtonFrame.origin.y), CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), value: value) + self.backButtonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) + } + + func applyPushAnimationProgress(nextItemState nextItemState: NavigationItemTransitionState, value: CGFloat) { + let titleStartPosition = self.titlePosition + let titleStartAlpha: CGFloat = 1.0 + var titleEndPosition = CGPoint(x: -self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) + if let nextItemBackButtonPosition = nextItemState.backButtonPosition { + titleEndPosition = nextItemBackButtonPosition + } + let titleEndAlpha: CGFloat = 0.0 + + self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) + self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) + + self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) + self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) + + self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), CGPoint(x: -self.backButtonLabelFrame.size.width - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), value: value) + self.backButtonNode.label.alpha = self.interpolateValue(1.0, 0.0, value: value) + self.backButtonNode.arrow.alpha = self.interpolateValue(1.0, nextItemState.backButtonPosition == nil ? 0.0 : 1.0, value: value) + } + + func applyPopAnimationProgress(previousItemState previousItemState: NavigationItemTransitionState, value: CGFloat) { + var titleStartPosition = CGPoint(x: -self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) + if let previousItemBackButtonPosition = previousItemState.backButtonPosition { + titleStartPosition = previousItemBackButtonPosition + } + let titleStartAlpha: CGFloat = 0.0 + let titleEndPosition = self.titlePosition + let titleEndAlpha: CGFloat = 1.0 + self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) + self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) + + self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) + self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) + + self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: -self.backButtonLabelFrame.size.width - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), value: value) + self.backButtonNode.label.alpha = self.interpolateValue(0.0, 1.0, value: value) + self.backButtonNode.arrow.alpha = self.interpolateValue(previousItemState.backButtonPosition == nil ? 0.0 : 1.0, 1.0, value: value) + } + + func applyPopAnimationProgress(nextItemState nextItemState: NavigationItemTransitionState, value: CGFloat) { + let titleStartPosition = self.titlePosition + let titleStartAlpha: CGFloat = 1.0 + let titleEndPosition = CGPoint(x: self.parentNode.frame.size.width + self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) + let titleEndAlpha: CGFloat = 0.0 + self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) + self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) + + self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) + self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) + + self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), CGPoint(x: nextItemState.titlePosition.x - self.backButtonFrame.origin.x, y: nextItemState.titlePosition.y - self.backButtonFrame.origin.y), value: value) + self.backButtonNode.label.alpha = self.interpolateValue(1.0, 0.0, value: value) + self.backButtonNode.arrow.alpha = self.interpolateValue(1.0, 0.0, value: value) + } + + func animatePush(previousItemWrapper: NavigationItemWrapper?, duration: NSTimeInterval) { + if let previousItemWrapper = previousItemWrapper { + self.suspendLayout = true + self.backButtonNode.suspendLayout = true + + let transitionState = self.transitionState + let previousItemState = previousItemWrapper.transitionState + + self.applyPushAnimationProgress(previousItemState: previousItemState, value: 0.0) + previousItemWrapper.applyPushAnimationProgress(nextItemState: transitionState, value: 0.0) + + UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { () -> Void in + self.applyPushAnimationProgress(previousItemState: previousItemState, value: 1.0) + previousItemWrapper.applyPushAnimationProgress(nextItemState: transitionState, value: 1.0) + }, completion: { completed in + self.suspendLayout = false + self.backButtonNode.suspendLayout = false + + previousItemWrapper.applyPushAnimationProgress(nextItemState: self.transitionState, value: 1.0) + }) + } + } + + func animatePop(previousItemWrapper: NavigationItemWrapper?, duration: NSTimeInterval) { + if let previousItemWrapper = previousItemWrapper { + self.applyPopAnimationProgress(previousItemState: previousItemWrapper.transitionState, value: 0.0) + previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: 0.0) + + UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { () -> Void in + self.applyPopAnimationProgress(previousItemState: previousItemWrapper.transitionState, value: 1.0) + previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: 1.0) + }, completion: { completed in + previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: 0.0) + }) + } + } + + func setInteractivePopProgress(progress: CGFloat, previousItemWrapper: NavigationItemWrapper) { + self.applyPopAnimationProgress(previousItemState: previousItemWrapper.transitionState, value: progress) + previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: progress) + } +} diff --git a/Display/NavigationTitleNode.swift b/Display/NavigationTitleNode.swift new file mode 100644 index 0000000000..f268dd3871 --- /dev/null +++ b/Display/NavigationTitleNode.swift @@ -0,0 +1,50 @@ +import UIKit +import AsyncDisplayKit + +public class NavigationTitleNode: ASDisplayNode { + private let label: ASTextNode + + private var _text: NSString = "" + public var text: NSString { + get { + return self._text + } + set(value) { + self._text = value + self.setText(value) + } + } + + public init(text: NSString) { + self.label = ASTextNode() + self.label.displaysAsynchronously = false + + super.init() + + self.addSubnode(self.label) + + self.setText(text) + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setText(text: NSString) { + var titleAttributes = [String : AnyObject]() + titleAttributes[NSFontAttributeName] = UIFont.boldSystemFontOfSize(17.0) + titleAttributes[NSForegroundColorAttributeName] = UIColor.blackColor() + let titleString = NSAttributedString(string: text as String, attributes: titleAttributes) + self.label.attributedString = titleString + self.invalidateCalculatedLayout() + } + + public override func calculateSizeThatFits(constrainedSize: CGSize) -> CGSize { + self.label.measure(constrainedSize) + return self.label.calculatedSize + } + + public override func layout() { + self.label.frame = self.bounds + } +} diff --git a/Display/NavigationTransitionView.swift b/Display/NavigationTransitionView.swift new file mode 100644 index 0000000000..46d081c2d6 --- /dev/null +++ b/Display/NavigationTransitionView.swift @@ -0,0 +1,82 @@ +import UIKit + +class NavigationTransitionCoordinator { + private var _progress: CGFloat = 0.0 + var progress: CGFloat { + get { + return self._progress + } + set(value) { + self._progress = value + self.navigationBar.setInteractivePopProgress(value) + self.updateProgress() + } + } + + private let container: UIView + private let topView: UIView + private let topViewSuperview: UIView? + private let bottomView: UIView + private let dimView: UIView + private let navigationBar: NavigationBar + + init(container: UIView, topView: UIView, bottomView: UIView, navigationBar: NavigationBar) { + self.container = container + self.topView = topView + self.topViewSuperview = topView.superview + self.bottomView = bottomView + self.dimView = UIView() + self.dimView.backgroundColor = UIColor.blackColor() + self.navigationBar = navigationBar + + if let topViewSuperview = self.topViewSuperview { + topViewSuperview.insertSubview(bottomView, belowSubview: topView) + } + + self.updateProgress() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateProgress() { + self.topView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(self.progress * self.container.bounds.size.width), y: 0.0), size: self.container.bounds.size) + self.dimView.frame = self.container.bounds + self.dimView.alpha = (1.0 - self.progress) * 0.1 + self.bottomView.frame = CGRect(origin: CGPoint(x: ((self.progress - 1.0) * self.container.bounds.size.width * 0.3), y: 0.0), size: self.container.bounds.size) + } + + func animateCancel(completion: () -> ()) { + UIView.animateWithDuration(0.1, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in + self.progress = 0.0 + }) { (completed) -> Void in + if let topViewSuperview = self.topViewSuperview { + topViewSuperview.addSubview(self.topView) + } + else { + self.topView.removeFromSuperview() + } + self.bottomView.removeFromSuperview() + + completion() + } + } + + func animateCompletion(velocity: CGFloat, completion: () -> ()) { + let distance = (1.0 - self.progress) * self.container.bounds.size.width + UIView.animateWithDuration(NSTimeInterval(max(0.05, min(0.2, abs(distance / velocity)))), delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in + self.progress = 1.0 + }) { (completed) -> Void in + if let topViewSuperview = self.topViewSuperview { + topViewSuperview.addSubview(self.topView) + } + else { + self.topView.removeFromSuperview() + } + self.bottomView.removeFromSuperview() + + completion() + } + } +} \ No newline at end of file diff --git a/Display/NotificationCenterUtils.h b/Display/NotificationCenterUtils.h new file mode 100644 index 0000000000..09409f3fc7 --- /dev/null +++ b/Display/NotificationCenterUtils.h @@ -0,0 +1,9 @@ +#import + +typedef bool (^NotificationHandlerBlock)(NSString *, id, NSDictionary *); + +@interface NotificationCenterUtils : NSObject + ++ (void)addNotificationHandler:(NotificationHandlerBlock)handler; + +@end diff --git a/Display/NotificationCenterUtils.m b/Display/NotificationCenterUtils.m new file mode 100644 index 0000000000..7088967d19 --- /dev/null +++ b/Display/NotificationCenterUtils.m @@ -0,0 +1,51 @@ +#import "NotificationCenterUtils.h" + +#import "RuntimeUtils.h" + +static NSMutableArray *notificationHandlers() +{ + static NSMutableArray *array = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + array = [[NSMutableArray alloc] init]; + }); + return array; +} + +@interface NSNotificationCenter (_a65afc19) + +@end + +@implementation NSNotificationCenter (_a65afc19) + +- (void)_a65afc19_postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo +{ + for (NotificationHandlerBlock handler in notificationHandlers()) + { + if (handler(aName, anObject, aUserInfo)) + return; + } + + [self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo]; +} + +@end + +@implementation NotificationCenterUtils + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[NSNotificationCenter class] currentSelector:@selector(postNotificationName:object:userInfo:) newSelector:@selector(_a65afc19_postNotificationName:object:userInfo:)]; + }); +} + ++ (void)addNotificationHandler:(bool (^)(NSString *, id, NSDictionary *))handler +{ + [notificationHandlers() addObject:[handler copy]]; +} + +@end diff --git a/Display/RuntimeUtils.h b/Display/RuntimeUtils.h new file mode 100644 index 0000000000..8f6e180282 --- /dev/null +++ b/Display/RuntimeUtils.h @@ -0,0 +1,20 @@ +#import + +typedef enum { + NSObjectAssociationPolicyRetain = 0, + NSObjectAssociationPolicyCopy = 1 +} NSObjectAssociationPolicy; + +@interface RuntimeUtils : NSObject + ++ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector; + +@end + +@interface NSObject (AssociatedObject) + +- (void)setAssociatedObject:(id)object forKey:(void const *)key; +- (void)setAssociatedObject:(id)object forKey:(void const *)key associationPolicy:(NSObjectAssociationPolicy)associationPolicy; +- (id)associatedObjectForKey:(void const *)key; + +@end diff --git a/Display/RuntimeUtils.m b/Display/RuntimeUtils.m new file mode 100644 index 0000000000..da122a4a38 --- /dev/null +++ b/Display/RuntimeUtils.m @@ -0,0 +1,56 @@ +#import "RuntimeUtils.h" + +#import + +@implementation RuntimeUtils + ++ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector +{ + Method origMethod = nil, newMethod = nil; + + origMethod = class_getInstanceMethod(targetClass, currentSelector); + newMethod = class_getInstanceMethod(targetClass, newSelector); + if ((origMethod != nil) && (newMethod != nil)) + { + if(class_addMethod(targetClass, currentSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) + { + class_replaceMethod(targetClass, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); + } + else + method_exchangeImplementations(origMethod, newMethod); + } +} + +@end + +@implementation NSObject (AssociatedObject) + +- (void)setAssociatedObject:(id)object forKey:(void const *)key +{ + [self setAssociatedObject:object forKey:key associationPolicy:NSObjectAssociationPolicyRetain]; +} + +- (void)setAssociatedObject:(id)object forKey:(void const *)key associationPolicy:(NSObjectAssociationPolicy)associationPolicy +{ + int policy = 0; + switch (associationPolicy) + { + case NSObjectAssociationPolicyRetain: + policy = OBJC_ASSOCIATION_RETAIN_NONATOMIC; + break; + case NSObjectAssociationPolicyCopy: + policy = OBJC_ASSOCIATION_COPY_NONATOMIC; + break; + default: + policy = OBJC_ASSOCIATION_RETAIN_NONATOMIC; + break; + } + objc_setAssociatedObject(self, key, object, policy); +} + +- (id)associatedObjectForKey:(void const *)key +{ + return objc_getAssociatedObject(self, key); +} + +@end diff --git a/Display/UIBarButtonItem+Proxy.h b/Display/UIBarButtonItem+Proxy.h new file mode 100644 index 0000000000..031bf15fb3 --- /dev/null +++ b/Display/UIBarButtonItem+Proxy.h @@ -0,0 +1,15 @@ +#import + +typedef void (^UIBarButtonItemSetTitleListener)(NSString *); +typedef void (^UIBarButtonItemSetEnabledListener)(BOOL); + +@interface UIBarButtonItem (Proxy) + +- (void)performActionOnTarget; + +- (NSInteger)addSetTitleListener:(UIBarButtonItemSetTitleListener)listener; +- (void)removeSetTitleListener:(NSInteger)key; +- (NSInteger)addSetEnabledListener:(UIBarButtonItemSetEnabledListener)listener; +- (void)removeSetEnabledListener:(NSInteger)key; + +@end diff --git a/Display/UIBarButtonItem+Proxy.m b/Display/UIBarButtonItem+Proxy.m new file mode 100644 index 0000000000..01e6ad0e0c --- /dev/null +++ b/Display/UIBarButtonItem+Proxy.m @@ -0,0 +1,83 @@ +#import "UIBarButtonItem+Proxy.h" + +#import "NSBag.h" +#import "RuntimeUtils.h" + +static const void *setEnabledListenerBagKey = &setEnabledListenerBagKey; +static const void *setTitleListenerBagKey = &setTitleListenerBagKey; + +@implementation UIBarButtonItem (Proxy) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[UIBarButtonItem class] currentSelector:@selector(setEnabled:) newSelector:@selector(_c1e56039_setEnabled:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIBarButtonItem class] currentSelector:@selector(setTitle:) newSelector:@selector(_c1e56039_setTitle:)]; + }); +} + +- (void)_c1e56039_setEnabled:(BOOL)enabled +{ + [self _c1e56039_setEnabled:enabled]; + + [(NSBag *)[self associatedObjectForKey:setEnabledListenerBagKey] enumerateItems:^(UIBarButtonItemSetEnabledListener listener) + { + listener(enabled); + }]; +} + +- (void)_c1e56039_setTitle:(NSString *)title +{ + [self _c1e56039_setTitle:title]; + + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UIBarButtonItemSetTitleListener listener) + { + listener(title); + }]; +} + +- (void)performActionOnTarget +{ + NSAssert(self.target != nil, @"self.target != nil"); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [self.target performSelector:self.action]; +#pragma clang diagnostic pop +} + +- (NSInteger)addSetTitleListener:(UIBarButtonItemSetTitleListener)listener +{ + NSBag *bag = [self associatedObjectForKey:setTitleListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setTitleListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetTitleListener:(NSInteger)key +{ + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] removeItem:key]; +} + +- (NSInteger)addSetEnabledListener:(UIBarButtonItemSetEnabledListener)listener +{ + NSBag *bag = [self associatedObjectForKey:setEnabledListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setEnabledListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetEnabledListener:(NSInteger)key +{ + [(NSBag *)[self associatedObjectForKey:setEnabledListenerBagKey] removeItem:key]; +} + +@end diff --git a/Display/UIKitUtils.h b/Display/UIKitUtils.h new file mode 100644 index 0000000000..d53da326e3 --- /dev/null +++ b/Display/UIKitUtils.h @@ -0,0 +1,7 @@ +#import + +@interface UIView (AnimationUtils) + ++ (double)animationDurationFactor; + +@end diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m new file mode 100644 index 0000000000..c8cbb8aed8 --- /dev/null +++ b/Display/UIKitUtils.m @@ -0,0 +1,18 @@ +#import "UIKitUtils.h" + +#if TARGET_IPHONE_SIMULATOR +UIKIT_EXTERN float UIAnimationDragCoefficient(); // UIKit private drag coeffient, use judiciously +#endif + +@implementation UIView (AnimationUtils) + ++ (double)animationDurationFactor +{ +#if TARGET_IPHONE_SIMULATOR + return (double)UIAnimationDragCoefficient(); +#endif + + return 1.0f; +} + +@end diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift new file mode 100644 index 0000000000..46e1fc8a89 --- /dev/null +++ b/Display/UIKitUtils.swift @@ -0,0 +1,31 @@ +import UIKit + +public func dumpViews(view: UIView) { + dumpViews(view, indent: "") +} + +private func dumpViews(view: UIView, indent: String = "") { + print("\(indent)\(view)") + let nextIndent = indent + "-" + for subview in view.subviews { + dumpViews(subview as UIView, indent: nextIndent) + } +} + +public func dumpLayers(layer: CALayer) { + dumpLayers(layer, indent: "") +} + +private func dumpLayers(layer: CALayer, indent: String = "") { + print("\(indent)\(layer)(\(layer.frame))") + if layer.sublayers != nil { + let nextIndent = indent + ".." + for sublayer in layer.sublayers ?? [] { + dumpLayers(sublayer as CALayer, indent: nextIndent) + } + } +} + +public func floorToScreenPixels(value: CGFloat) -> CGFloat { + return floor(value * 2.0) / 2.0 +} diff --git a/Display/UINavigationItem+Proxy.h b/Display/UINavigationItem+Proxy.h new file mode 100644 index 0000000000..0c2a5bd37a --- /dev/null +++ b/Display/UINavigationItem+Proxy.h @@ -0,0 +1,15 @@ +#import + +typedef void (^UINavigationItemSetTitleListener)(NSString *); +typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem *, BOOL); + +@interface UINavigationItem (Proxy) + +- (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener; +- (void)removeSetTitleListener:(NSInteger)key; +- (NSInteger)addSetLeftBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; +- (void)removeSetLeftBarButtonItemListener:(NSInteger)key; +- (NSInteger)addSetRightBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; +- (void)removeSetRightBarButtonItemListener:(NSInteger)key; + +@end diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m new file mode 100644 index 0000000000..d6944828dc --- /dev/null +++ b/Display/UINavigationItem+Proxy.m @@ -0,0 +1,101 @@ +#import "UINavigationItem+Proxy.h" + +#import "NSBag.h" +#import "RuntimeUtils.h" + +static const void *setTitleListenerBagKey = &setTitleListenerBagKey; +static const void *setLeftBarButtonItemListenerBagKey = &setLeftBarButtonItemListenerBagKey; +static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemListenerBagKey; + +@implementation UINavigationItem (Proxy) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setTitle:) newSelector:@selector(_ac91f40f_setTitle:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setLeftBarButtonItem:) newSelector:@selector(_ac91f40f_setLeftBarButtonItem:animated:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:animated:)]; + }); +} + +- (void)_ac91f40f_setTitle:(NSString *)title +{ + [self _ac91f40f_setTitle:title]; + + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UINavigationItemSetTitleListener listener) + { + listener(title); + }]; +} + +- (void)_ac91f40f_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem animated:(BOOL)animated +{ + [self _ac91f40f_setLeftBarButtonItem:leftBarButtonItem animated:animated]; + + [(NSBag *)[self associatedObjectForKey:setLeftBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) + { + listener(leftBarButtonItem, animated); + }]; +} + +- (void)_ac91f40f_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem animated:(BOOL)animated +{ + [self _ac91f40f_setRightBarButtonItem:rightBarButtonItem animated:animated]; + + [(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) + { + listener(rightBarButtonItem, animated); + }]; +} + +- (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener +{ + NSBag *bag = [self associatedObjectForKey:setTitleListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setTitleListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetTitleListener:(NSInteger)key +{ + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] removeItem:key]; +} + +- (NSInteger)addSetLeftBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener +{ + NSBag *bag = [self associatedObjectForKey:setLeftBarButtonItemListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setLeftBarButtonItemListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetLeftBarButtonItemListener:(NSInteger)key +{ + [(NSBag *)[self associatedObjectForKey:setLeftBarButtonItemListenerBagKey] removeItem:key]; +} + +- (NSInteger)addSetRightBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener +{ + NSBag *bag = [self associatedObjectForKey:setRightBarButtonItemListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setRightBarButtonItemListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetRightBarButtonItemListener:(NSInteger)key +{ + [(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] removeItem:key]; +} + +@end diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h new file mode 100644 index 0000000000..8f5a15d055 --- /dev/null +++ b/Display/UIViewController+Navigation.h @@ -0,0 +1,7 @@ +#import + +@interface UIViewController (Navigation) + +- (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations; + +@end diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m new file mode 100644 index 0000000000..565c3ec385 --- /dev/null +++ b/Display/UIViewController+Navigation.m @@ -0,0 +1,55 @@ +#import "UIViewController+Navigation.h" + +#import "RuntimeUtils.h" + +static const void *UIViewControllerIgnoreAppearanceMethodInvocationsKey = &UIViewControllerIgnoreAppearanceMethodInvocationsKey; + +@implementation UIViewController (Navigation) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewWillAppear:) newSelector:@selector(_65087dc8_viewWillAppear:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewDidAppear:) newSelector:@selector(_65087dc8_viewDidAppear:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewWillDisappear:) newSelector:@selector(_65087dc8_viewWillDisappear:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewDidDisappear:) newSelector:@selector(_65087dc8_viewDidDisappear:)]; + }); +} + +- (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations +{ + [self setAssociatedObject:@(ignoreAppearanceMethodInvocations) forKey:UIViewControllerIgnoreAppearanceMethodInvocationsKey]; +} + +- (BOOL)ignoreAppearanceMethodInvocations +{ + return [[self associatedObjectForKey:UIViewControllerIgnoreAppearanceMethodInvocationsKey] boolValue]; +} + +- (void)_65087dc8_viewWillAppear:(BOOL)animated +{ + if (![self ignoreAppearanceMethodInvocations]) + [self _65087dc8_viewWillAppear:animated]; +} + +- (void)_65087dc8_viewDidAppear:(BOOL)animated +{ + if (![self ignoreAppearanceMethodInvocations]) + [self _65087dc8_viewDidAppear:animated]; +} + +- (void)_65087dc8_viewWillDisappear:(BOOL)animated +{ + if (![self ignoreAppearanceMethodInvocations]) + [self _65087dc8_viewWillDisappear:animated]; +} + +- (void)_65087dc8_viewDidDisappear:(BOOL)animated +{ + if (![self ignoreAppearanceMethodInvocations]) + [self _65087dc8_viewDidDisappear:animated]; +} + +@end diff --git a/Display/UIWindow+OrientationChange.h b/Display/UIWindow+OrientationChange.h new file mode 100644 index 0000000000..3a48232217 --- /dev/null +++ b/Display/UIWindow+OrientationChange.h @@ -0,0 +1,13 @@ +#import + +@interface UIWindow (OrientationChange) + +- (bool)isRotating; + +@end + +@interface UINavigationBar (Condensed) + +- (void)setCondensed:(BOOL)condensed; + +@end diff --git a/Display/UIWindow+OrientationChange.m b/Display/UIWindow+OrientationChange.m new file mode 100644 index 0000000000..1c7946f765 --- /dev/null +++ b/Display/UIWindow+OrientationChange.m @@ -0,0 +1,85 @@ +#import "UIWindow+OrientationChange.h" + +#import "RuntimeUtils.h" +#import "NotificationCenterUtils.h" + +static const void *isRotatingKey = &isRotatingKey; + +@implementation UIWindow (OrientationChange) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [NotificationCenterUtils addNotificationHandler:^bool(NSString *name, id object, NSDictionary *userInfo) + { + if ([name isEqualToString:@"UIWindowWillRotateNotification"]) + { + [(UIWindow *)object setRotating:true]; + + if (NSClassFromString(@"NSUserActivity") == NULL) + { + UIInterfaceOrientation orientation = [userInfo[@"UIWindowNewOrientationUserInfoKey"] integerValue]; + CGSize screenSize = [UIScreen mainScreen].bounds.size; + if (screenSize.width > screenSize.height) + { + CGFloat tmp = screenSize.height; + screenSize.height = screenSize.width; + screenSize.width = tmp; + } + CGSize windowSize = CGSizeZero; + CGFloat windowRotation = 0.0; + bool landscape = false; + switch (orientation) { + case UIInterfaceOrientationPortrait: + windowSize = screenSize; + break; + case UIInterfaceOrientationPortraitUpsideDown: + windowRotation = (CGFloat)(M_PI); + windowSize = screenSize; + break; + case UIInterfaceOrientationLandscapeLeft: + landscape = true; + windowRotation = (CGFloat)(-M_PI / 2.0); + windowSize = CGSizeMake(screenSize.height, screenSize.width); + break; + case UIInterfaceOrientationLandscapeRight: + landscape = true; + windowRotation = (CGFloat)(M_PI / 2.0); + windowSize = CGSizeMake(screenSize.height, screenSize.width); + break; + default: + break; + } + + [UIView animateWithDuration:0.3 animations:^ + { + CGAffineTransform transform = CGAffineTransformIdentity; + transform = CGAffineTransformRotate(transform, windowRotation); + ((UIWindow *)object).transform = transform; + ((UIWindow *)object).bounds = CGRectMake(0.0f, 0.0f, windowSize.width, windowSize.height); + }]; + } + } + else if ([name isEqualToString:@"UIWindowDidRotateNotification"]) + { + [(UIWindow *)object setRotating:false]; + } + + return false; + }]; + }); +} + +- (void)setRotating:(bool)rotating +{ + [self setAssociatedObject:@(rotating) forKey:isRotatingKey]; +} + +- (bool)isRotating +{ + return [[self associatedObjectForKey:isRotatingKey] boolValue]; +} + +@end diff --git a/Display/ViewController.swift b/Display/ViewController.swift new file mode 100644 index 0000000000..cd2d61e3f6 --- /dev/null +++ b/Display/ViewController.swift @@ -0,0 +1,51 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +@objc public class ViewController: UIViewController, WindowContentController { + private var _displayNode: ASDisplayNode? + public var displayNode: ASDisplayNode { + get { + if let value = self._displayNode { + return value + } + else { + self.loadDisplayNode() + if self._displayNode == nil { + fatalError("displayNode should be initialized after loadDisplayNode()") + } + return self._displayNode! + } + } + set(value) { + self._displayNode = value + } + } + + public init() { + super.init(nibName: nil, bundle: nil) + + self.automaticallyAdjustsScrollViewInsets = false + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func loadView() { + self.view = self.displayNode.view + } + + public func loadDisplayNode() { + self.displayNode = ASDisplayNode() + } + + public func setViewSize(toSize: CGSize, duration: NSTimeInterval) { + if duration > DBL_EPSILON { + animateRotation(self.displayNode, toFrame: CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height), duration: duration) + } + else { + self.displayNode.frame = CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height) + } + } +} diff --git a/Display/Window.swift b/Display/Window.swift new file mode 100644 index 0000000000..0b2ae25ccd --- /dev/null +++ b/Display/Window.swift @@ -0,0 +1,134 @@ +import Foundation +import AsyncDisplayKit + +public class WindowRootViewController: UIViewController { + public override func preferredStatusBarStyle() -> UIStatusBarStyle { + return .Default + } + + public override func prefersStatusBarHidden() -> Bool { + return false + } +} + +@objc +public protocol WindowContentController { + func setViewSize(toSize: CGSize, duration: NSTimeInterval) + var view: UIView! { get } +} + +public func animateRotation(view: UIView?, toFrame: CGRect, duration: NSTimeInterval) { + if let view = view { + UIView.animateWithDuration(duration, animations: { () -> Void in + view.frame = toFrame + }) + } +} + +public func animateRotation(view: ASDisplayNode?, toFrame: CGRect, duration: NSTimeInterval) { + if let view = view { + CALayer.beginRecordingChanges() + view.frame = toFrame + view.layout() + let states = CALayer.endRecordingChanges() as! [CALayerAnimation] + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + for state in states { + if let layer = state.layer { + if !CGRectEqualToRect(state.startBounds, state.endBounds) { + let boundsAnimation = CABasicAnimation(keyPath: "bounds") + boundsAnimation.fromValue = NSValue(CGRect: state.startBounds) + boundsAnimation.toValue = NSValue(CGRect: state.endBounds) + boundsAnimation.duration = duration + boundsAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + boundsAnimation.removedOnCompletion = true + boundsAnimation.fillMode = kCAFillModeForwards + boundsAnimation.speed = speed + layer.addAnimation(boundsAnimation, forKey: "_rotationBounds") + } + + if !CGPointEqualToPoint(state.startPosition, state.endPosition) { + let positionAnimation = CABasicAnimation(keyPath: "position") + positionAnimation.fromValue = NSValue(CGPoint: state.startPosition) + positionAnimation.toValue = NSValue(CGPoint: state.endPosition) + positionAnimation.duration = duration + positionAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + positionAnimation.removedOnCompletion = true + positionAnimation.fillMode = kCAFillModeForwards + positionAnimation.speed = speed + layer.addAnimation(positionAnimation, forKey: "_rotationPosition") + } + } + } + } +} + +public class Window: UIWindow { + public convenience init() { + self.init(frame: UIScreen.mainScreen().bounds) + } + + public override init(frame: CGRect) { + super.init(frame: frame) + + super.rootViewController = WindowRootViewController() + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { + return self.viewController?.view.hitTest(point, withEvent: event) + } + + public override var frame: CGRect { + get { + return super.frame + } + set(value) { + let sizeUpdated = super.frame.size != value.size + super.frame = value + if sizeUpdated { + self.viewController?.setViewSize(value.size, duration: self.isRotating() ? 0.3 : 0.0) + } + } + } + + public override var bounds: CGRect { + get { + return super.frame + } + set(value) { + let sizeUpdated = super.bounds.size != value.size + super.bounds = value + if sizeUpdated { + self.viewController?.setViewSize(value.size, duration: self.isRotating() ? 0.3 : 0.0) + } + } + } + + private var _rootViewController: WindowContentController? + public var viewController: WindowContentController? { + get { + return _rootViewController + } + set(value) { + self._rootViewController?.view.removeFromSuperview() + self._rootViewController = value + self._rootViewController?.view.frame = self.bounds + /*if let reactiveController = self._rootViewController as? ReactiveViewController { + reactiveController.displayNode.frame = CGRect(x: 0.0, y: 0.0, width: self.frame.size.width, height: self.frame.size.height) + self.addSubview(reactiveController.displayNode.view) + } + else {*/ + if let view = self._rootViewController?.view { + self.addSubview(view) + } + //} + } + } +} diff --git a/submodules/AsyncDisplayKit b/submodules/AsyncDisplayKit new file mode 160000 index 0000000000..5482213e2e --- /dev/null +++ b/submodules/AsyncDisplayKit @@ -0,0 +1 @@ +Subproject commit 5482213e2ee8b880ebc23024ccc4643e1491e071