From 147dfe39cacbd5a305dd9367888c1c115d70d4e1 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 9 Jan 2018 13:17:48 +0400 Subject: [PATCH] no message --- Display.xcodeproj/project.pbxproj | 230 ++++++- .../xcschemes/xcschememanagement.plist | 6 +- Display/ActionSheetButtonItem.swift | 3 +- Display/ActionSheetControllerNode.swift | 20 +- Display/CAAnimationUtils.swift | 6 +- Display/ChildWindowHostView.swift | 2 +- Display/ContainableController.swift | 412 ------------- Display/ContainedViewLayoutTransition.swift | 560 ++++++++++++++++++ Display/GenerateImage.swift | 3 + Display/KeyboardManager.swift | 22 +- Display/ListView.swift | 404 ++++++++++--- Display/ListViewAccessoryItemNode.swift | 14 +- Display/ListViewAnimation.swift | 2 + Display/ListViewFloatingHeaderNode.swift | 8 + Display/ListViewIntermediateState.swift | 4 + Display/ListViewItem.swift | 8 +- Display/ListViewItemHeader.swift | 37 +- Display/ListViewItemNode.swift | 109 ++-- .../ListViewOverscrollBackgroundNode.swift | 31 + Display/ListViewScroller.swift | 3 + Display/ListViewScrollerAppkit.swift | 5 - Display/MergedLayoutEvents.swift | 9 - Display/NativeWindowHostView.swift | 48 ++ Display/NavigationBar.swift | 93 ++- Display/NavigationController.swift | 11 +- Display/StatusBar.swift | 6 +- Display/StatusBarManager.swift | 12 +- Display/StatusBarProxyNode.swift | 2 +- ...ainedControllerTransitionCoordinator.swift | 73 --- Display/TabBarContollerNode.swift | 13 +- Display/TabBarNode.swift | 149 +++-- Display/TextAlertController.swift | 21 +- Display/UIKitUtils.m | 31 +- Display/UIKitUtils.swift | 38 ++ Display/UIViewController+Navigation.h | 7 +- Display/UIViewController+Navigation.m | 10 +- Display/ViewController.swift | 13 +- Display/WindowContent.swift | 128 +++- DisplayMac/ASDisplayNode.swift | 84 +++ DisplayMac/NSValueAdditions.swift | 12 + DisplayMac/UIGestureRecognizer.swift | 47 ++ DisplayMac/UIKit.swift | 71 +++ DisplayMac/UIScrollView.swift | 24 + DisplayMac/UISlider.swift | 5 + DisplayMac/UITouch.swift | 5 + DisplayMac/UIView.swift | 49 ++ 46 files changed, 2042 insertions(+), 808 deletions(-) create mode 100644 Display/ContainedViewLayoutTransition.swift create mode 100644 Display/ListViewFloatingHeaderNode.swift create mode 100644 Display/ListViewOverscrollBackgroundNode.swift delete mode 100644 Display/ListViewScrollerAppkit.swift delete mode 100644 Display/MergedLayoutEvents.swift delete mode 100644 Display/SystemContainedControllerTransitionCoordinator.swift create mode 100644 DisplayMac/ASDisplayNode.swift create mode 100644 DisplayMac/NSValueAdditions.swift create mode 100644 DisplayMac/UIGestureRecognizer.swift create mode 100644 DisplayMac/UIKit.swift create mode 100644 DisplayMac/UIScrollView.swift create mode 100644 DisplayMac/UISlider.swift create mode 100644 DisplayMac/UITouch.swift create mode 100644 DisplayMac/UIView.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index e9eca87f96..6f0aefa93f 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -10,11 +10,32 @@ D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */; }; D01159BB1F40E96C0039383E /* DisplayMac.h in Headers */ = {isa = PBXBuildFile; fileRef = D01159B91F40E96C0039383E /* DisplayMac.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D01159C21F40EA120039383E /* ListViewScrollerAppkit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01159C11F40EA120039383E /* ListViewScrollerAppkit.swift */; }; D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7511D1AE08D00E269B5 /* ContainableController.swift */; }; - D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */; }; D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7571D1B467200E269B5 /* ActionSheetController.swift */; }; D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */; }; + D01847611FFA703B00075256 /* UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847601FFA703B00075256 /* UIKit.swift */; }; + D01847631FFA70FC00075256 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847621FFA70FC00075256 /* UIView.swift */; }; + D01847641FFA723600075256 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */; }; + D01847661FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */; }; + D01847671FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */; }; + D01847681FFA749F00075256 /* ListViewAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */; }; + D01847691FFA756600075256 /* ListViewAccessoryItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */; }; + D018476A1FFA75EE00075256 /* Spring.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBD1CC4431D0044FF83 /* Spring.swift */; }; + D018476D1FFA765D00075256 /* NSValueAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018476B1FFA765D00075256 /* NSValueAdditions.swift */; }; + D018476E1FFA76DC00075256 /* ListViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */; }; + D018476F1FFA76FD00075256 /* ListViewAccessoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */; }; + D01847701FFA773100075256 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBE1CC4431D0044FF83 /* ListView.swift */; }; + D01847711FFA778100075256 /* ListViewIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */; }; + D01847741FFA780400075256 /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847721FFA780400075256 /* UIScrollView.swift */; }; + D01847751FFA78B200075256 /* ListViewScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */; }; + D01847771FFA78C100075256 /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847761FFA78C100075256 /* UIGestureRecognizer.swift */; }; + D01847791FFA7A4E00075256 /* UITouch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847781FFA7A4E00075256 /* UITouch.swift */; }; + D018477A1FFA7A8800075256 /* ListViewOverscrollBackgroundNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */; }; + D018477C1FFA7ABF00075256 /* UISlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018477B1FFA7ABF00075256 /* UISlider.swift */; }; + D01C06C21FC254F8001561AB /* ASDisplayNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C06C11FC254F8001561AB /* ASDisplayNode.swift */; }; + D01C06C31FC2552C001561AB /* ListViewItemHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */; }; + D01C06C41FC25561001561AB /* ListViewItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */; }; + D01C06C51FC2558F001561AB /* SwiftSignalKitMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D01C06C61FC2558F001561AB /* SwiftSignalKitMac.framework */; }; D01E2BDE1D9049620066BF65 /* GridNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BDD1D9049620066BF65 /* GridNode.swift */; }; D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */; }; D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE11D9049F60066BF65 /* GridItemNode.swift */; }; @@ -42,7 +63,6 @@ D053CB601D22B4F200DD41DF /* CATracingLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */; }; D05BE4AB1D1F25E3002BD72C /* PresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */; }; - D05BE4AE1D217F6B002BD72C /* MergedLayoutEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05BE4AD1D217F6B002BD72C /* MergedLayoutEvents.swift */; }; 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 */; }; @@ -90,6 +110,8 @@ D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */; }; D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */; }; D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A749941E3A9E7B00AD786E /* SwitchNode.swift */; }; + D0AA840E1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA840D1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift */; }; + D0AA84101FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */; }; D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; }; D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */; }; D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; @@ -150,11 +172,19 @@ D01159B71F40E96B0039383E /* DisplayMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DisplayMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D01159B91F40E96C0039383E /* DisplayMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DisplayMac.h; sourceTree = ""; }; D01159BA1F40E96C0039383E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D01159C11F40EA120039383E /* ListViewScrollerAppkit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewScrollerAppkit.swift; sourceTree = ""; }; D015F7511D1AE08D00E269B5 /* ContainableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainableController.swift; sourceTree = ""; }; - D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemContainedControllerTransitionCoordinator.swift; sourceTree = ""; }; D015F7571D1B467200E269B5 /* ActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = ""; }; D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetControllerNode.swift; sourceTree = ""; }; + D01847601FFA703B00075256 /* UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKit.swift; sourceTree = ""; }; + D01847621FFA70FC00075256 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; + D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainedViewLayoutTransition.swift; sourceTree = ""; }; + D018476B1FFA765D00075256 /* NSValueAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSValueAdditions.swift; sourceTree = ""; }; + D01847721FFA780400075256 /* UIScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollView.swift; sourceTree = ""; }; + D01847761FFA78C100075256 /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; + D01847781FFA7A4E00075256 /* UITouch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITouch.swift; sourceTree = ""; }; + D018477B1FFA7ABF00075256 /* UISlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISlider.swift; sourceTree = ""; }; + D01C06C11FC254F8001561AB /* ASDisplayNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASDisplayNode.swift; sourceTree = ""; }; + D01C06C61FC2558F001561AB /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D01E2BDD1D9049620066BF65 /* GridNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNode.swift; sourceTree = ""; }; D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNodeScroller.swift; sourceTree = ""; }; D01E2BE11D9049F60066BF65 /* GridItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItemNode.swift; sourceTree = ""; }; @@ -182,7 +212,6 @@ D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CATracingLayer.h; sourceTree = ""; }; D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CATracingLayer.m; sourceTree = ""; }; D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationContext.swift; sourceTree = ""; }; - D05BE4AD1D217F6B002BD72C /* MergedLayoutEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MergedLayoutEvents.swift; sourceTree = ""; }; D05CC2631B69316F00E235A3 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D05CC2661B69316F00E235A3 /* Display.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Display.h; sourceTree = ""; }; D05CC2681B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -233,6 +262,8 @@ D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightTrackingButton.swift; sourceTree = ""; }; D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetCheckboxItem.swift; sourceTree = ""; }; D0A749941E3A9E7B00AD786E /* SwitchNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchNode.swift; sourceTree = ""; }; + D0AA840D1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewFloatingHeaderNode.swift; sourceTree = ""; }; + D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewOverscrollBackgroundNode.swift; sourceTree = ""; }; D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = ""; }; D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionState.swift; sourceTree = ""; }; D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; @@ -283,6 +314,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D01C06C51FC2558F001561AB /* SwiftSignalKitMac.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -311,6 +343,14 @@ children = ( D01159B91F40E96C0039383E /* DisplayMac.h */, D01159BA1F40E96C0039383E /* Info.plist */, + D01C06C11FC254F8001561AB /* ASDisplayNode.swift */, + D01847601FFA703B00075256 /* UIKit.swift */, + D01847621FFA70FC00075256 /* UIView.swift */, + D01847721FFA780400075256 /* UIScrollView.swift */, + D018476B1FFA765D00075256 /* NSValueAdditions.swift */, + D01847761FFA78C100075256 /* UIGestureRecognizer.swift */, + D01847781FFA7A4E00075256 /* UITouch.swift */, + D018477B1FFA7ABF00075256 /* UISlider.swift */, ); path = DisplayMac; sourceTree = ""; @@ -379,6 +419,7 @@ isa = PBXGroup; children = ( D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */, + D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */, D0E35A021DE473B900BC6096 /* HighlightableButton.swift */, D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */, D0A749941E3A9E7B00AD786E /* SwitchNode.swift */, @@ -410,13 +451,11 @@ D05BE4AC1D217F33002BD72C /* Utils */ = { isa = PBXGroup; children = ( - D05BE4AD1D217F6B002BD72C /* MergedLayoutEvents.swift */, - D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */, D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */, D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */, - D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */, D05174B11EAA833200A1BF36 /* CASeeThroughTracingLayer.h */, D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */, + D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */, ); name = Utils; sourceTree = ""; @@ -472,6 +511,7 @@ D05CC2A31B6932D500E235A3 /* Frameworks */ = { isa = PBXGroup; children = ( + D01C06C61FC2558F001561AB /* SwiftSignalKitMac.framework */, D0C2DFFB1CC528B70044FF83 /* SwiftSignalKit.framework */, D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */, D0E1D6351CBC159C00B04029 /* AVFoundation.framework */, @@ -609,9 +649,10 @@ D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */, D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */, D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */, - D01159C11F40EA120039383E /* ListViewScrollerAppkit.swift */, D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */, D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */, + D0AA840D1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift */, + D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */, ); name = "List Node"; sourceTree = ""; @@ -816,7 +857,27 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D01159C21F40EA120039383E /* ListViewScrollerAppkit.swift in Sources */, + D01847691FFA756600075256 /* ListViewAccessoryItemNode.swift in Sources */, + D01847611FFA703B00075256 /* UIKit.swift in Sources */, + D01847701FFA773100075256 /* ListView.swift in Sources */, + D01847771FFA78C100075256 /* UIGestureRecognizer.swift in Sources */, + D01C06C21FC254F8001561AB /* ASDisplayNode.swift in Sources */, + D01847631FFA70FC00075256 /* UIView.swift in Sources */, + D01847791FFA7A4E00075256 /* UITouch.swift in Sources */, + D01C06C41FC25561001561AB /* ListViewItemNode.swift in Sources */, + D018476D1FFA765D00075256 /* NSValueAdditions.swift in Sources */, + D01847641FFA723600075256 /* CAAnimationUtils.swift in Sources */, + D018476A1FFA75EE00075256 /* Spring.swift in Sources */, + D01C06C31FC2552C001561AB /* ListViewItemHeader.swift in Sources */, + D018477C1FFA7ABF00075256 /* UISlider.swift in Sources */, + D01847671FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */, + D018476E1FFA76DC00075256 /* ListViewItem.swift in Sources */, + D01847751FFA78B200075256 /* ListViewScroller.swift in Sources */, + D01847711FFA778100075256 /* ListViewIntermediateState.swift in Sources */, + D018476F1FFA76FD00075256 /* ListViewAccessoryItem.swift in Sources */, + D018477A1FFA7A8800075256 /* ListViewOverscrollBackgroundNode.swift in Sources */, + D01847741FFA780400075256 /* UIScrollView.swift in Sources */, + D01847681FFA749F00075256 /* ListViewAnimation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -885,6 +946,7 @@ D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */, D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */, + D01847661FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */, D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */, D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */, D01E2BE41D904A000066BF65 /* GridItem.swift in Sources */, @@ -898,14 +960,15 @@ D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */, D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, + D0AA840E1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift in Sources */, D0C2DFCD1CC4431D0044FF83 /* ListViewTransactionQueue.swift in Sources */, + D0AA84101FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift in Sources */, D02383821DDF798E004018B6 /* LegacyPresentedControllerNode.swift in Sources */, D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */, D0CE67921F7DA11700FFB557 /* ActionSheetTheme.swift in Sources */, - D05BE4AE1D217F6B002BD72C /* MergedLayoutEvents.swift in Sources */, D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */, D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */, D036574B1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift in Sources */, @@ -919,7 +982,6 @@ D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */, D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, D05CC2A21B69326C00E235A3 /* WindowContent.swift in Sources */, - D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */, D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */, D02383801DDF7916004018B6 /* LegacyPresentedController.swift in Sources */, @@ -1527,6 +1589,144 @@ }; name = "Release AppStore"; }; + D0924FD41FE52BE9003F693F /* Release Hockeyapp Internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release Hockeyapp Internal"; + }; + D0924FD51FE52BE9003F693F /* Release Hockeyapp Internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; + 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"; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 4.0; + }; + name = "Release Hockeyapp Internal"; + }; + D0924FD61FE52BE9003F693F /* Release Hockeyapp Internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = DisplayTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; + }; + name = "Release Hockeyapp Internal"; + }; + D0924FD71FE52BE9003F693F /* Release Hockeyapp Internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = DisplayMac/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release Hockeyapp Internal"; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1536,6 +1736,7 @@ D01159BC1F40E96C0039383E /* Debug Hockeyapp */, D01159BD1F40E96C0039383E /* Debug AppStore */, D01159BE1F40E96C0039383E /* Release Hockeyapp */, + D0924FD71FE52BE9003F693F /* Release Hockeyapp Internal */, D01159BF1F40E96C0039383E /* Release AppStore */, ); defaultConfigurationIsVisible = 0; @@ -1547,6 +1748,7 @@ D05CC2751B69316F00E235A3 /* Debug Hockeyapp */, D079FD091F06BD9C0038FADE /* Debug AppStore */, D05CC2761B69316F00E235A3 /* Release Hockeyapp */, + D0924FD41FE52BE9003F693F /* Release Hockeyapp Internal */, D086A56E1CC0115D00F08284 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; @@ -1558,6 +1760,7 @@ D05CC2781B69316F00E235A3 /* Debug Hockeyapp */, D079FD0A1F06BD9C0038FADE /* Debug AppStore */, D05CC2791B69316F00E235A3 /* Release Hockeyapp */, + D0924FD51FE52BE9003F693F /* Release Hockeyapp Internal */, D086A56F1CC0115D00F08284 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; @@ -1569,6 +1772,7 @@ D05CC27B1B69316F00E235A3 /* Debug Hockeyapp */, D079FD0B1F06BD9C0038FADE /* Debug AppStore */, D05CC27C1B69316F00E235A3 /* Release Hockeyapp */, + D0924FD61FE52BE9003F693F /* Release Hockeyapp Internal */, D086A5701CC0115D00F08284 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 7a4a41da85..704fdb6c69 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,17 +7,17 @@ Display.xcscheme orderHint - 22 + 23 DisplayMac.xcscheme orderHint - 25 + 26 DisplayTests.xcscheme orderHint - 23 + 24 SuppressBuildableAutocreation diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift index ac5e3b9cbe..e34e4ec1f2 100644 --- a/Display/ActionSheetButtonItem.swift +++ b/Display/ActionSheetButtonItem.swift @@ -55,6 +55,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { self.label.isLayerBacked = true self.label.maximumNumberOfLines = 1 self.label.displaysAsynchronously = false + self.label.truncationMode = .byTruncatingTail super.init(theme: theme) @@ -108,7 +109,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { self.button.frame = CGRect(origin: CGPoint(), size: size) - let labelSize = self.label.measure(size) + let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 10.0), height: size.height)) self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) } diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index 7f9fb5e152..90830cbe5a 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -25,6 +25,8 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { var dismiss: () -> Void = { } + private var validLayout: ContainerViewLayout? + init(theme: ActionSheetControllerTheme) { self.theme = theme @@ -76,7 +78,11 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - let insets = layout.insets(options: [.statusBar]) + var insets = layout.insets(options: [.statusBar]) + insets.left += layout.safeInsets.left + insets.right += layout.safeInsets.right + + self.validLayout = layout self.scrollView.frame = CGRect(origin: CGPoint(), size: layout.size) self.dismissTapView.frame = CGRect(origin: CGPoint(), size: layout.size) @@ -85,7 +91,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { self.itemGroupsContainerNode.frame = CGRect(origin: CGPoint(x: insets.left + containerInsets.left, y: layout.size.height - insets.bottom - containerInsets.bottom - self.itemGroupsContainerNode.calculatedSize.height), size: self.itemGroupsContainerNode.calculatedSize) self.itemGroupsContainerNode.layout() - self.updateScrollDimViews(size: layout.size) + self.updateScrollDimViews(size: layout.size, safeInsets: layout.safeInsets) } func animateIn() { @@ -135,7 +141,9 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } func scrollViewDidScroll(_ scrollView: UIScrollView) { - self.updateScrollDimViews(size: self.scrollView.frame.size) + if let validLayout = self.validLayout { + self.updateScrollDimViews(size: validLayout.size, safeInsets: validLayout.safeInsets) + } } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { @@ -147,15 +155,15 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - func updateScrollDimViews(size: CGSize) { + func updateScrollDimViews(size: CGSize, safeInsets: UIEdgeInsets) { let additionalTopHeight = max(0.0, -self.scrollView.contentOffset.y) let additionalBottomHeight = -min(0.0, -self.scrollView.contentOffset.y) self.topDimView.frame = CGRect(x: containerInsets.left, y: -additionalTopHeight, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, self.itemGroupsContainerNode.frame.minY + additionalTopHeight)) self.bottomDimView.frame = CGRect(x: containerInsets.left, y: self.itemGroupsContainerNode.frame.maxY, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, size.height - self.itemGroupsContainerNode.frame.maxY + additionalBottomHeight)) - self.leftDimView.frame = CGRect(x: 0.0, y: -additionalTopHeight, width: containerInsets.left, height: size.height + additionalTopHeight + additionalBottomHeight) - self.rightDimView.frame = CGRect(x: size.width - containerInsets.right, y: -additionalTopHeight, width: containerInsets.right, height: size.height + additionalTopHeight + additionalBottomHeight) + self.leftDimView.frame = CGRect(x: 0.0, y: -additionalTopHeight, width: containerInsets.left + safeInsets.left, height: size.height + additionalTopHeight + additionalBottomHeight) + self.rightDimView.frame = CGRect(x: size.width - containerInsets.right, y: -additionalTopHeight, width: containerInsets.right + safeInsets.right, height: size.height + additionalTopHeight + additionalBottomHeight) } func setGroups(_ groups: [ActionSheetItemGroup]) { diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index de7cf0a653..140dc121e6 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -1,4 +1,8 @@ -import UIKit +#if os(macOS) + import Cocoa +#else + import UIKit +#endif @objc private class CALayerAnimationDelegate: NSObject, CAAnimationDelegate { var completion: ((Bool) -> Void)? diff --git a/Display/ChildWindowHostView.swift b/Display/ChildWindowHostView.swift index d2faf8c6e0..071334be6a 100644 --- a/Display/ChildWindowHostView.swift +++ b/Display/ChildWindowHostView.swift @@ -32,8 +32,8 @@ public func childWindowHostView(parent: UIView) -> WindowHostView { let hostView = WindowHostView(view: view, isRotating: { return false }, updateSupportedInterfaceOrientations: { orientations in - //rootViewController.orientations = orientations }, updateDeferScreenEdgeGestures: { edges in + }, updatePreferNavigationUIHidden: { value in }) view.updateSize = { [weak hostView] size in diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index f77ca45763..fd0aba9305 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -1,418 +1,6 @@ import UIKit import AsyncDisplayKit -public enum ContainedViewLayoutTransitionCurve { - case easeInOut - case spring -} - -public extension ContainedViewLayoutTransitionCurve { - var timingFunction: String { - switch self { - case .easeInOut: - return kCAMediaTimingFunctionEaseInEaseOut - case .spring: - return kCAMediaTimingFunctionSpring - } - } - - var viewAnimationOptions: UIViewAnimationOptions { - switch self { - case .easeInOut: - return [.curveEaseInOut] - case .spring: - return UIViewAnimationOptions(rawValue: 7 << 16) - } - } -} - -public enum ContainedViewLayoutTransition { - case immediate - case animated(duration: Double, curve: ContainedViewLayoutTransitionCurve) - - public var isAnimated: Bool { - if case .immediate = self { - return false - } else { - return true - } - } -} - -public extension ContainedViewLayoutTransition { - func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { - if node.frame.equalTo(frame) && !force { - completion?(true) - } else { - switch self { - case .immediate: - node.frame = frame - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousFrame = node.frame - node.frame = frame - node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func updateBounds(node: ASDisplayNode, bounds: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { - if node.bounds.equalTo(bounds) && !force { - completion?(true) - } else { - switch self { - case .immediate: - node.bounds = bounds - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousBounds = node.bounds - node.bounds = bounds - node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func updatePosition(node: ASDisplayNode, position: CGPoint, completion: ((Bool) -> Void)? = nil) { - if node.position.equalTo(position) { - completion?(true) - } else { - switch self { - case .immediate: - node.position = position - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousPosition = node.position - node.position = position - node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func animatePosition(node: ASDisplayNode, from position: CGPoint, completion: ((Bool) -> Void)? = nil) { - switch self { - case .immediate: - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func animatePosition(node: ASDisplayNode, to position: CGPoint, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - if node.position.equalTo(position) { - completion?(true) - } else { - switch self { - case .immediate: - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func animateFrame(node: ASDisplayNode, from frame: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - switch self { - case .immediate: - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.animateFrame(from: frame, to: node.layer.frame, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - switch self { - case .immediate: - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - layer.animateBounds(from: bounds, to: layer.bounds, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) { - switch self { - case .immediate: - break - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - node.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction) - } - } - - func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) { - switch self { - case .immediate: - completion?() - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction, completion: { _ in - completion?() - }) - } - } - - func animatePositionAdditive(node: ASDisplayNode, offset: CGFloat) { - switch self { - case .immediate: - break - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) - } - } - - func updateFrame(view: UIView, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { - if view.frame.equalTo(frame) && !force { - completion?(true) - } else { - switch self { - case .immediate: - view.frame = frame - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousFrame = view.frame - view.frame = frame - view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) { - if layer.frame.equalTo(frame) { - completion?(true) - } else { - switch self { - case .immediate: - layer.frame = frame - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousFrame = layer.frame - layer.frame = frame - layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func updateAlpha(node: ASDisplayNode, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { - if node.alpha.isEqual(to: alpha) { - if let completion = completion { - completion(true) - } - return - } - - switch self { - case .immediate: - node.alpha = alpha - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousAlpha = node.alpha - node.alpha = alpha - node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { - if layer.opacity.isEqual(to: Float(alpha)) { - if let completion = completion { - completion(true) - } - return - } - - switch self { - case .immediate: - layer.opacity = Float(alpha) - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousAlpha = layer.opacity - layer.opacity = Float(alpha) - layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func updateBackgroundColor(node: ASDisplayNode, color: UIColor, completion: ((Bool) -> Void)? = nil) { - if let nodeColor = node.backgroundColor, nodeColor.isEqual(color) { - if let completion = completion { - completion(true) - } - return - } - - switch self { - case .immediate: - node.backgroundColor = color - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - if let nodeColor = node.backgroundColor { - node.backgroundColor = color - node.layer.animate(from: nodeColor.cgColor, to: color.cgColor, keyPath: "backgroundColor", timingFunction: curve.timingFunction, duration: duration, completion: { result in - if let completion = completion { - completion(result) - } - }) - } else { - node.backgroundColor = color - if let completion = completion { - completion(true) - } - } - } - } - - func updateTransformScale(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { - let t = node.layer.transform - let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) - if currentScale.isEqual(to: scale) { - if let completion = completion { - completion(true) - } - return - } - - switch self { - case .immediate: - node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) - node.layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func updateSublayerTransformOffset(layer: CALayer, offset: CGPoint, completion: ((Bool) -> Void)? = nil) { - print("update to \(offset) animated: \(self.isAnimated)") - let t = layer.transform - let currentOffset = CGPoint(x: t.m41, y: t.m42) - if currentOffset == offset { - if let completion = completion { - completion(true) - } - return - } - - switch self { - case .immediate: - layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) - layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { - result in - if let completion = completion { - completion(result) - } - }) - } - } -} - -public extension ContainedViewLayoutTransition { - public func animateView(_ f: @escaping () -> Void) { - switch self { - case .immediate: - f() - case let .animated(duration, curve): - UIView.animate(withDuration: duration, delay: 0.0, options: curve.viewAnimationOptions, animations: { - f() - }, completion: nil) - } - } -} - public protocol ContainableController: class { var view: UIView! { get } diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift new file mode 100644 index 0000000000..0a8f51cd1c --- /dev/null +++ b/Display/ContainedViewLayoutTransition.swift @@ -0,0 +1,560 @@ +import Foundation + +#if os(macOS) + import QuartzCore +#else + import AsyncDisplayKit +#endif + +public enum ContainedViewLayoutTransitionCurve { + case easeInOut + case spring +} + +public extension ContainedViewLayoutTransitionCurve { + var timingFunction: String { + switch self { + case .easeInOut: + return kCAMediaTimingFunctionEaseInEaseOut + case .spring: + return kCAMediaTimingFunctionSpring + } + } + + #if os(iOS) + var viewAnimationOptions: UIViewAnimationOptions { + switch self { + case .easeInOut: + return [.curveEaseInOut] + case .spring: + return UIViewAnimationOptions(rawValue: 7 << 16) + } + } + #endif +} + +public enum ContainedViewLayoutTransition { + case immediate + case animated(duration: Double, curve: ContainedViewLayoutTransitionCurve) + + public var isAnimated: Bool { + if case .immediate = self { + return false + } else { + return true + } + } +} + +public extension ContainedViewLayoutTransition { + func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if node.frame.equalTo(frame) && !force { + completion?(true) + } else { + switch self { + case .immediate: + node.frame = frame + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousFrame = node.frame + node.frame = frame + node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func updateBounds(node: ASDisplayNode, bounds: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if node.bounds.equalTo(bounds) && !force { + completion?(true) + } else { + switch self { + case .immediate: + node.bounds = bounds + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousBounds = node.bounds + node.bounds = bounds + node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func updatePosition(node: ASDisplayNode, position: CGPoint, completion: ((Bool) -> Void)? = nil) { + if node.position.equalTo(position) { + completion?(true) + } else { + switch self { + case .immediate: + node.position = position + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousPosition = node.position + node.position = position + node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)? = nil) { + if layer.position.equalTo(position) { + completion?(true) + } else { + switch self { + case .immediate: + layer.position = position + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousPosition = layer.position + layer.position = position + layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func animatePosition(node: ASDisplayNode, from position: CGPoint, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func animatePosition(node: ASDisplayNode, to position: CGPoint, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + if node.position.equalTo(position) { + completion?(true) + } else { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func animateFrame(node: ASDisplayNode, from frame: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animateFrame(from: frame, to: node.layer.frame, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + layer.animateBounds(from: bounds, to: layer.bounds, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + node.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction) + } + } + + func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) { + switch self { + case .immediate: + completion?() + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction, completion: { _ in + completion?() + }) + } + } + + func animatePositionAdditive(node: ASDisplayNode, offset: CGFloat) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) + } + } + + func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, completion: (() -> Void)? = nil) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true, completion: { _ in + completion?() + }) + } + } + + func animatePositionAdditive(layer: CALayer, offset: CGPoint, completion: (() -> Void)? = nil) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true, completion: { _ in + completion?() + }) + } + } + + func updateFrame(view: UIView, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if view.frame.equalTo(frame) && !force { + completion?(true) + } else { + switch self { + case .immediate: + view.frame = frame + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousFrame = view.frame + view.frame = frame + view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) { + if layer.frame.equalTo(frame) { + completion?(true) + } else { + switch self { + case .immediate: + layer.frame = frame + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousFrame = layer.frame + layer.frame = frame + layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func updateAlpha(node: ASDisplayNode, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { + if node.alpha.isEqual(to: alpha) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.alpha = alpha + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousAlpha = node.alpha + node.alpha = alpha + node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { + if layer.opacity.isEqual(to: Float(alpha)) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + layer.opacity = Float(alpha) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousAlpha = layer.opacity + layer.opacity = Float(alpha) + layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateBackgroundColor(node: ASDisplayNode, color: UIColor, completion: ((Bool) -> Void)? = nil) { + if let nodeColor = node.backgroundColor, nodeColor.isEqual(color) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.backgroundColor = color + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + if let nodeColor = node.backgroundColor { + node.backgroundColor = color + node.layer.animate(from: nodeColor.cgColor, to: color.cgColor, keyPath: "backgroundColor", timingFunction: curve.timingFunction, duration: duration, completion: { result in + if let completion = completion { + completion(result) + } + }) + } else { + node.backgroundColor = color + if let completion = completion { + completion(true) + } + } + } + } + + func animateTransformScale(node: ASDisplayNode, from fromScale: CGFloat, completion: ((Bool) -> Void)? = nil) { + let t = node.layer.transform + let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + if currentScale.isEqual(to: fromScale) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animateScale(from: fromScale, to: currentScale, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateTransformScale(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { + let t = node.layer.transform + let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + if currentScale.isEqual(to: scale) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + node.layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateTransformScale(layer: CALayer, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { + let t = layer.transform + let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + if currentScale.isEqual(to: scale) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateSublayerTransformScale(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { + let t = node.layer.sublayerTransform + let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + if currentScale.isEqual(to: scale) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0) + node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { + result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateSublayerTransformOffset(layer: CALayer, offset: CGPoint, completion: ((Bool) -> Void)? = nil) { + print("update to \(offset) animated: \(self.isAnimated)") + let t = layer.transform + let currentOffset = CGPoint(x: t.m41, y: t.m42) + if currentOffset == offset { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) + layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { + result in + if let completion = completion { + completion(result) + } + }) + } + } +} + +#if os(iOS) + +public extension ContainedViewLayoutTransition { + public func animateView(_ f: @escaping () -> Void) { + switch self { + case .immediate: + f() + case let .animated(duration, curve): + UIView.animate(withDuration: duration, delay: 0.0, options: curve.viewAnimationOptions, animations: { + f() + }, completion: nil) + } + } +} + +#endif diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 833a594ac0..0115279078 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -297,6 +297,9 @@ public class DrawingContext { self.provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in free(bytes) }) + + assert(self.bytesPerRow % 16 == 0) + assert(unsafeBitCast(self.bytes, to: Int64.self) % 16 == 0) } public func generateImage() -> UIImage? { diff --git a/Display/KeyboardManager.swift b/Display/KeyboardManager.swift index dfca12345d..d77b09d346 100644 --- a/Display/KeyboardManager.swift +++ b/Display/KeyboardManager.swift @@ -35,6 +35,13 @@ class KeyboardManager { self.host = host } + func getCurrentKeyboardHeight() -> CGFloat { + guard let keyboardView = self.host.keyboardView else { + return 0.0 + } + return keyboardView.bounds.height + } + func updateInteractiveInputOffset(_ offset: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { guard let keyboardView = self.host.keyboardView else { return @@ -60,24 +67,31 @@ class KeyboardManager { } var firstResponderView: UIView? - var firstResponderDisablesAutomaticKeyboardHandling = false + var firstResponderDisableAutomaticKeyboardHandling: UIResponderDisableAutomaticKeyboardHandling = [] for surface in self.surfaces { if let view = getFirstResponder(surface.host) { firstResponderView = surface.host - firstResponderDisablesAutomaticKeyboardHandling = view.disablesAutomaticKeyboardHandling + firstResponderDisableAutomaticKeyboardHandling = view.disableAutomaticKeyboardHandling break } } if let firstResponderView = firstResponderView { let containerOrigin = firstResponderView.convert(CGPoint(), to: nil) - let horizontalTranslation = CATransform3DMakeTranslation(firstResponderDisablesAutomaticKeyboardHandling ? 0.0 : containerOrigin.x, 0.0, 0.0) + var filteredTranslation = containerOrigin.x + if firstResponderDisableAutomaticKeyboardHandling.contains(.forward) { + filteredTranslation = max(0.0, filteredTranslation) + } + if firstResponderDisableAutomaticKeyboardHandling.contains(.backward) { + filteredTranslation = min(0.0, filteredTranslation) + } + let horizontalTranslation = CATransform3DMakeTranslation(filteredTranslation, 0.0, 0.0) let currentTransform = keyboardWindow.layer.sublayerTransform if !CATransform3DEqualToTransform(horizontalTranslation, currentTransform) { //print("set to \(CGPoint(x: containerOrigin.x, y: self.interactiveInputOffset))") keyboardWindow.layer.sublayerTransform = horizontalTranslation } - if let tracingLayer = firstResponderView.layer as? CATracingLayer, !firstResponderDisablesAutomaticKeyboardHandling { + if let tracingLayer = firstResponderView.layer as? CATracingLayer, firstResponderDisableAutomaticKeyboardHandling.isEmpty { if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource, previousPositionAnimationMirrorSource !== tracingLayer { previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil) } diff --git a/Display/ListView.swift b/Display/ListView.swift index c978a5f14a..8896f99983 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1,6 +1,10 @@ +#if os(macOS) +import SwiftSignalKitMac +#else import UIKit import AsyncDisplayKit import SwiftSignalKit +#endif private let usePerformanceTracker = false private let useDynamicTuning = false @@ -97,10 +101,30 @@ public enum ListViewVisibleContentOffset { case none } +public struct ListViewKeepTopItemOverscrollBackground { + public let color: UIColor + public let direction: Bool + + public init(color: UIColor, direction: Bool) { + self.color = color + self.direction = direction + } + + fileprivate func isEqual(to: ListViewKeepTopItemOverscrollBackground) -> Bool { + if !self.color.isEqual(to.color) { + return false + } + if self.direction != to.direction { + return false + } + return true + } +} + open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private final let scroller: ListViewScroller private final var visibleSize: CGSize = CGSize() - private final var insets = UIEdgeInsets() + public private(set) final var insets = UIEdgeInsets() private final var lastContentOffset: CGPoint = CGPoint() private final var lastContentOffsetTimestamp: CFAbsoluteTime = 0.0 private final var ignoreScrollingEvents: Bool = false @@ -124,12 +148,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public final var stackFromBottom: Bool = false public final var stackFromBottomInsetItemFactor: CGFloat = 0.0 public final var limitHitTestToNodes: Bool = false - public final var keepTopItemOverscrollBackground: UIColor? { + public final var keepTopItemOverscrollBackground: ListViewKeepTopItemOverscrollBackground? { didSet { - if let color = self.keepTopItemOverscrollBackground { - self.topItemOverscrollBackground?.backgroundColor = color + if let value = self.keepTopItemOverscrollBackground { + self.topItemOverscrollBackground?.color = value.color } - self.updateTopItemOverscrollBackground() + self.updateTopItemOverscrollBackground(transition: .immediate) } } public final var keepBottomItemOverscrollBackground: UIColor? { @@ -142,7 +166,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } public final var snapToBottomInsetUntilFirstInteraction: Bool = false - private var topItemOverscrollBackground: ASDisplayNode? + public final var updateFloatingHeaderOffset: ((CGFloat, ContainedViewLayoutTransition) -> Void)? { + didSet { + + } + } + + private var topItemOverscrollBackground: ListViewOverscrollBackgroundNode? private var bottomItemOverscrollBackground: ASDisplayNode? private var touchesPosition = CGPoint() @@ -186,6 +216,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var selectionTouchLocation: CGPoint? private var selectionTouchDelayTimer: Foundation.Timer? + private var selectionLongTapDelayTimer: Foundation.Timer? private var flashNodesDelayTimer: Foundation.Timer? private var highlightedItemIndex: Int? @@ -290,18 +321,23 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.displayLink.invalidate() if useBackgroundDeallocation { - for itemNode in self.itemNodes { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + assertionFailure() + /*for itemNode in self.itemNodes { + ASDeallocQueue.sharedDeallocation.releaseObject(inBackground: UnsafeMutablePointer(itemNode)) } for itemHeaderNode in self.itemHeaderNodes { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemHeaderNode) - } + ASDeallocQueue.sharedDeallocatio.releaseObject(inBackground: itemHeaderNode) + }*/ } else { - for itemNode in self.itemNodes { - ASPerformMainThreadDeallocation(itemNode) + for i in (0 ..< self.itemNodes.count).reversed() { + var itemNode: AnyObject? = self.itemNodes[i] + self.itemNodes.remove(at: i) + ASPerformMainThreadDeallocation(&itemNode) } - for itemHeaderNode in self.itemHeaderNodes { - ASPerformMainThreadDeallocation(itemHeaderNode) + for key in self.itemHeaderNodes.keys { + var itemHeaderNode: AnyObject? = self.itemHeaderNodes[key] + self.itemHeaderNodes.removeValue(forKey: key) + ASPerformMainThreadDeallocation(&itemHeaderNode) } } @@ -443,7 +479,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let deltaY = scrollView.contentOffset.y - self.lastContentOffset.y self.lastContentOffset = scrollView.contentOffset - if self.lastContentOffsetTimestamp > DBL_EPSILON { + if !self.lastContentOffsetTimestamp.isZero { self.lastContentOffsetTimestamp = CACurrentMediaTime() } @@ -494,14 +530,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if !self.snapToBounds(snapTopItem: false, stackFromBottom: self.stackFromBottom).offset.isZero { self.updateVisibleContentOffset() } - self.updateScroller() + self.updateScroller(transition: .immediate) - self.updateItemHeaders() + self.updateItemHeaders(leftInset: self.insets.left, rightInset: self.insets.right) for (_, headerNode) in self.itemHeaderNodes { - //let position = headerNode.position - //headerNode.position = CGPoint(x: position.x, y: position.y - deltaY) - if headerNode.wantsScrollDynamics { useScrollDynamics = true @@ -692,6 +725,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = itemNode.frame frame.origin.y += offset itemNode.frame = frame + if let accessoryItemNode = itemNode.accessoryItemNode { + itemNode.layoutAccessoryItemNode(accessoryItemNode, leftInset: self.insets.left, rightInset: self.insets.right) + } } } @@ -731,15 +767,17 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.ignoreScrollingEvents = wasIgnoringScrollingEvents } - private func updateTopItemOverscrollBackground() { - if let color = self.keepTopItemOverscrollBackground { - let topItemOverscrollBackground: ASDisplayNode + private func updateTopItemOverscrollBackground(transition: ContainedViewLayoutTransition) { + if let value = self.keepTopItemOverscrollBackground { + var applyTransition = transition + + let topItemOverscrollBackground: ListViewOverscrollBackgroundNode if let current = self.topItemOverscrollBackground { topItemOverscrollBackground = current } else { - topItemOverscrollBackground = ASDisplayNode() + applyTransition = .immediate + topItemOverscrollBackground = ListViewOverscrollBackgroundNode(color: value.color) topItemOverscrollBackground.isLayerBacked = true - topItemOverscrollBackground.backgroundColor = color self.topItemOverscrollBackground = topItemOverscrollBackground self.insertSubnode(topItemOverscrollBackground, at: 0) } @@ -752,25 +790,85 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel topItemFound = true } + var backgroundFrame: CGRect + if topItemFound { let realTopItemEdge = itemNodes.first!.apparentFrame.origin.y let realTopItemEdgeOffset = max(0.0, realTopItemEdge) - let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.visibleSize.width, height: realTopItemEdgeOffset)) - if !backgroundFrame.equalTo(topItemOverscrollBackground.frame) { - topItemOverscrollBackground.frame = backgroundFrame + backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.visibleSize.width, height: realTopItemEdgeOffset)) + if value.direction { + backgroundFrame.origin.y = 0.0 + backgroundFrame.size.height = realTopItemEdgeOffset + } else { + backgroundFrame.origin.y = min(self.insets.top, realTopItemEdgeOffset) + backgroundFrame.size.height = max(0.0, self.visibleSize.height - backgroundFrame.origin.y) + 400.0 } } else { - let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.visibleSize.width, height: 0.0)) - if !backgroundFrame.equalTo(topItemOverscrollBackground.frame) { - topItemOverscrollBackground.frame = backgroundFrame + backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.visibleSize.width, height: 0.0)) + if value.direction { + backgroundFrame.origin.y = 0.0 + } else { + backgroundFrame.origin.y = 0.0 + backgroundFrame.size.height = self.visibleSize.height } } + + let previousFrame = topItemOverscrollBackground.frame + if !previousFrame.equalTo(backgroundFrame) { + topItemOverscrollBackground.frame = backgroundFrame + + let positionDelta = CGPoint(x: backgroundFrame.minX - previousFrame.minX, y: backgroundFrame.minY - previousFrame.minY) + + applyTransition.animateOffsetAdditive(node: topItemOverscrollBackground, offset: positionDelta.y) + } + + topItemOverscrollBackground.updateLayout(size: backgroundFrame.size, transition: applyTransition) } else if let topItemOverscrollBackground = self.topItemOverscrollBackground { self.topItemOverscrollBackground = nil topItemOverscrollBackground.removeFromSupernode() } } + private func updateFloatingHeaderNode(transition: ContainedViewLayoutTransition) { + guard let updateFloatingHeaderOffset = self.updateFloatingHeaderOffset else { + return + } + + var topItemFound = false + var topItemNodeIndex: Int? + if !self.itemNodes.isEmpty { + topItemNodeIndex = self.itemNodes[0].index + } + if topItemNodeIndex == 0 { + topItemFound = true + } + + var topOffset: CGFloat + + if topItemFound { + let realTopItemEdge = itemNodes.first!.apparentFrame.origin.y + let realTopItemEdgeOffset = max(0.0, realTopItemEdge) + + topOffset = realTopItemEdgeOffset + } else { + if !self.itemNodes.isEmpty { + if self.stackFromBottom { + topOffset = 0.0 + } else { + topOffset = self.visibleSize.height + } + } else { + if self.stackFromBottom { + topOffset = self.visibleSize.height + } else { + topOffset = 0.0 + } + } + } + + updateFloatingHeaderOffset(topOffset, transition) + } + private func updateBottomItemOverscrollBackground() { if let color = self.keepBottomItemOverscrollBackground { var bottomItemFound = false @@ -812,7 +910,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func updateScroller() { + private func updateScroller(transition: ContainedViewLayoutTransition) { if itemNodes.count == 0 { return } @@ -862,8 +960,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - self.updateTopItemOverscrollBackground() + self.updateTopItemOverscrollBackground(transition: transition) self.updateBottomItemOverscrollBackground() + self.updateFloatingHeaderNode(transition: transition) let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true @@ -897,7 +996,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel DispatchQueue.global().async(execute: f) } - private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNode, ListViewItemNodeLayout, @escaping () -> (Signal?, () -> Void)) -> Void) { + private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNode, ListViewItemNodeLayout, @escaping () -> (Signal?, () -> Void)) -> Void) { if let previousNode = previousNode { item.updateNode(async: { f in if synchronous { @@ -905,7 +1004,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { self.async(f) } - }, node: previousNode, width: width, previousItem: previousItem, nextItem: nextItem, animation: updateAnimation, completion: { (layout, apply) in + }, node: previousNode, params: params, previousItem: previousItem, nextItem: nextItem, animation: updateAnimation, completion: { (layout, apply) in if Thread.isMainThread { if synchronous { completion(previousNode, layout, { @@ -934,13 +1033,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } }) } else { - item.nodeConfiguredForWidth(async: { f in + item.nodeConfiguredForParams(async: { f in if synchronous { f() } else { self.async(f) } - }, width: width, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in + }, params: params, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in itemNode.index = index completion(itemNode, ListViewItemNodeLayout(contentSize: itemNode.contentSize, insets: itemNode.insets), apply) }) @@ -1000,7 +1099,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.scroller.contentOffset = self.lastContentOffset self.ignoreScrollingEvents = wasIgnoringScrollingEvents - self.updateScroller() + self.updateScroller(transition: .immediate) completion() return @@ -1330,7 +1429,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { self.async(f) } - }, node: referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: updateAnimation, completion: { layout, apply in + }, node: referenceNode, params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: updateAnimation, completion: { layout, apply in var updatedState = state var updatedOperations = operations @@ -1398,14 +1497,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } if self.debugInfo { - print("insertionItemIndexAndDirection \(insertionItemIndexAndDirection)") + print("insertionItemIndexAndDirection \(String(describing: insertionItemIndexAndDirection))") } if let insertionItemIndexAndDirection = insertionItemIndexAndDirection { let index = insertionItemIndexAndDirection.0 let threadId = pthread_self() var tailRecurse = false - self.nodeForItem(synchronous: synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], width: state.visibleSize.width, updateAnimation: updateAnimation, completion: { (node, layout, apply) in + self.nodeForItem(synchronous: synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: updateAnimation, completion: { (node, layout, apply) in if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse { tailRecurse = true @@ -1441,7 +1540,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { let updateItem = updateIndicesAndItems[0] if let previousNode = previousNodes[updateItem.index] { - self.nodeForItem(synchronous: synchronous, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], width: state.visibleSize.width, updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in + self.nodeForItem(synchronous: synchronous, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, animation: animated ? .System(duration: insertionAnimationDuration) : .None, apply: apply, operations: &operations) updateIndicesAndItems.remove(at: 0) @@ -1471,7 +1570,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, forceAnimateInsertion: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void), timestamp: Double) { + private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, forceAnimateInsertion: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void), timestamp: Double, listInsets: UIEdgeInsets) { let insertionOrigin = self.referencePointForInsertionAtIndex(nodeIndex) let nodeOrigin: CGPoint @@ -1491,6 +1590,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.insets = layout.insets node.apparentHeight = animated ? 0.0 : layout.size.height node.frame = nodeFrame + if let accessoryItemNode = node.accessoryItemNode { + node.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } apply().1() self.itemNodes.insert(node, at: nodeIndex) @@ -1505,7 +1607,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let _ = previousFrame , animated && node.index != nil && nodeIndex != self.itemNodes.count - 1 { let nextNode = self.itemNodes[nodeIndex + 1] - if nextNode.index == nil { + if nextNode.index == nil && nextNode.subnodes.isEmpty { let nextHeight = nextNode.apparentHeight if abs(nextHeight - previousApparentHeight) < CGFloat.ulpOfOne { if let animation = nextNode.animationForKey("apparentHeight") { @@ -1539,10 +1641,29 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } if node.index == nil { + node.addHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) node.animateRemoved(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) } else if animated { - if !takenAnimation { + if takenAnimation { + if let previousFrame = previousFrame { + if self.debugInfo { + assert(true) + } + + let transitionOffsetDelta = nodeFrame.origin.y - previousFrame.origin.y + if node.rotated { + node.transitionOffset -= transitionOffsetDelta - previousApparentHeight + layout.size.height + } else { + node.transitionOffset += transitionOffsetDelta + } + node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + if previousInsets != layout.insets { + node.insets = previousInsets + node.addInsetsAnimationToValue(layout.insets, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } + } + } else { if !nodeFrame.size.height.isEqual(to: node.apparentHeight) { node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in if let node = node { @@ -1596,6 +1717,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = self.itemNodes[i].frame frame.origin.y -= offsetHeight self.itemNodes[i].frame = frame + if let accessoryItemNode = self.itemNodes[i].accessoryItemNode { + self.itemNodes[i].layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } i -= 1 } case .Down: @@ -1604,6 +1728,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = self.itemNodes[i].frame frame.origin.y += offsetHeight self.itemNodes[i].frame = frame + if let accessoryItemNode = self.itemNodes[i].accessoryItemNode { + self.itemNodes[i].layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } i += 1 } } @@ -1665,6 +1792,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { let timestamp = CACurrentMediaTime() + let listInsets = updateSizeAndInsets?.insets ?? self.insets + if let updateOpaqueState = updateOpaqueState { self.opaqueTransactionState = updateOpaqueState } @@ -1713,7 +1842,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel updatedPreviousFrame = nil } - self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) + self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp, listInsets: listInsets) if let _ = updatedPreviousFrame { if let lowestHeaderNode = lowestHeaderNode { self.insertSubnode(node, belowSubnode: lowestHeaderNode) @@ -1722,7 +1851,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } else { if animated { - self.insertSubnode(node, at: 0) + if let topItemOverscrollBackground = self.topItemOverscrollBackground { + self.insertSubnode(node, aboveSubnode: topItemOverscrollBackground) + } else { + self.insertSubnode(node, at: 0) + } } else { if let lowestHeaderNode = lowestHeaderNode { self.insertSubnode(node, belowSubnode: lowestHeaderNode) @@ -1745,10 +1878,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let height = height, let previousLayout = previousLayout { if takenPreviousNodes.contains(referenceNode) { - self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, {}) }, timestamp: timestamp) + self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets) } else { referenceNode.index = nil - self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, {}) }, timestamp: timestamp) + self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets) self.addSubnode(referenceNode) } } else { @@ -1772,6 +1905,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = self.itemNodes[i].frame frame.origin.y -= height self.itemNodes[i].frame = frame + if let accessoryItemNode = self.itemNodes[i].accessoryItemNode { + self.itemNodes[i].layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } } } case .Down: @@ -1780,6 +1916,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = self.itemNodes[i].frame frame.origin.y += height self.itemNodes[i].frame = frame + if let accessoryItemNode = self.itemNodes[i].accessoryItemNode { + self.itemNodes[i].layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } } } } @@ -1815,8 +1954,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } }) - let insetPart: CGFloat = previousInsets.top - layout.insets.top if node.rotated { + let insetPart: CGFloat = previousInsets.bottom - layout.insets.bottom node.transitionOffset += previousApparentHeight - layout.size.height - insetPart node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } @@ -1844,6 +1983,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + if let accessoryItemNode = node.accessoryItemNode { + node.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } + var index = 0 for itemNode in self.itemNodes { let offset = offsetRanges.offsetForIndex(index) @@ -1895,6 +2038,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = itemNode.frame frame.origin.y += offset itemNode.frame = frame + if let accessoryItemNode = itemNode.accessoryItemNode { + itemNode.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } } break @@ -1912,6 +2058,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = itemNode.frame frame.origin.y += offset itemNode.frame = frame + if let accessoryItemNode = itemNode.accessoryItemNode { + itemNode.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } } } @@ -1923,11 +2072,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } else if !additionalScrollDistance.isZero { self.stopScrolling() - /*for itemNode in self.itemNodes { - var frame = itemNode.frame - frame.origin.y += additionalScrollDistance - itemNode.frame = frame - }*/ } self.insertNodesInBatches(nodes: [], completion: { @@ -2092,9 +2236,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) + self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp, leftInset: listInsets.left, rightInset: listInsets.right) - if let scrollToItem = scrollToItem , scrollToItem.animated { + if let scrollToItem = scrollToItem, scrollToItem.animated { if self.itemNodes.count != 0 { var offset: CGFloat? @@ -2146,7 +2290,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel previousItemHeaderNodes.append(headerNode) } - self.updateItemHeaders(headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) if let offset = offset , abs(offset) > CGFloat.ulpOfOne { let lowestHeaderNode = self.lowestHeaderNode() @@ -2201,29 +2345,32 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel for itemNode in temporaryPreviousNodes { itemNode.removeFromSupernode() if useBackgroundDeallocation { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + assertionFailure() + //ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) } else { - ASPerformMainThreadDeallocation(itemNode) + //ASPerformMainThreadDeallocation(itemNode) } } for headerNode in temporaryHeaderNodes { headerNode.removeFromSupernode() if useBackgroundDeallocation { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: headerNode) + assertionFailure() + //ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: headerNode) } else { - ASPerformMainThreadDeallocation(headerNode) + //ASPerformMainThreadDeallocation(headerNode) } } } self.layer.add(animation, forKey: nil) } else { if useBackgroundDeallocation { - for itemNode in temporaryPreviousNodes { + assertionFailure() + /*for itemNode in temporaryPreviousNodes { ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) - } + }*/ } else { for itemNode in temporaryPreviousNodes { - ASPerformMainThreadDeallocation(itemNode) + //ASPerformMainThreadDeallocation(itemNode) } } } @@ -2231,39 +2378,50 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateItemNodesVisibilities() - self.updateScroller() + self.updateScroller(transition: headerNodesTransition.0) + + if let topItemOverscrollBackground = self.topItemOverscrollBackground { + headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: -headerNodesTransition.2) + } + self.setNeedsAnimations() self.updateVisibleContentOffset() if self.debugInfo { - let delta = CACurrentMediaTime() - timestamp + //let delta = CACurrentMediaTime() - timestamp //print("replayOperations \(delta * 1000.0) ms") } completion() } else { - self.updateItemHeaders(headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) self.updateItemNodesVisibilities() if animated { self.setNeedsAnimations() } - self.updateScroller() + self.updateScroller(transition: headerNodesTransition.0) + + if let topItemOverscrollBackground = self.topItemOverscrollBackground { + headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: -headerNodesTransition.2) + } + self.updateVisibleContentOffset() if self.debugInfo { - let delta = CACurrentMediaTime() - timestamp + //let delta = CACurrentMediaTime() - timestamp //print("replayOperations \(delta * 1000.0) ms") } for (previousNode, _) in previousApparentFrames { if previousNode.supernode == nil { if useBackgroundDeallocation { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: previousNode) + assertionFailure() + //ASDeallocQueue.sharedDeallocatio.releaseObject(inBackground: previousNode) } else { - ASPerformMainThreadDeallocation(previousNode) + //ASPerformMainThreadDeallocation(previousNode) } } } @@ -2303,12 +2461,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.removeFromSupernode() node.accessoryItemNode?.removeFromSupernode() - node.accessoryItemNode = nil + node.setAccessoryItemNode(nil, leftInset: self.insets.left, rightInset: self.insets.right) node.headerAccessoryItemNode?.removeFromSupernode() node.headerAccessoryItemNode = nil } - private func updateItemHeaders(_ transition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0), animateInsertion: Bool = false) { + private func updateItemHeaders(leftInset: CGFloat, rightInset: CGFloat, transition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0), animateInsertion: Bool = false) { let upperDisplayBound = self.insets.top let lowerDisplayBound = self.visibleSize.height - self.insets.bottom var visibleHeaderNodes = Set() @@ -2354,6 +2512,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } } + + headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset) headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: true) headerNode.internalStickLocationDistance = stickLocationDistance if !hasValidNodes && !headerNode.alpha.isZero { @@ -2372,6 +2532,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let headerNode = item.node() headerNode.updateFlashingOnScrolling(flashing, animated: false) headerNode.frame = headerFrame + headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset) headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: false) self.itemHeaderNodes[id] = headerNode self.addSubnode(headerNode) @@ -2432,7 +2593,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double) { + private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double, leftInset: CGFloat, rightInset: CGFloat) { var index = -1 let count = self.itemNodes.count for itemNode in self.itemNodes { @@ -2468,18 +2629,21 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel nextAccessoryItemNode.removeFromSupernode() itemNode.addSubnode(nextAccessoryItemNode) - itemNode.accessoryItemNode = nextAccessoryItemNode - self.itemNodes[i].accessoryItemNode = nil + + itemNode.setAccessoryItemNode(nextAccessoryItemNode, leftInset: leftInset, rightInset: rightInset) + self.itemNodes[i].setAccessoryItemNode(nil, leftInset: leftInset, rightInset: rightInset) var updatedAccessoryItemNodeOrigin = nextAccessoryItemNode.frame.origin - let updatedParentOrigin = itemNode.frame.origin + let updatedParentOrigin = itemNode.apparentFrame.origin updatedAccessoryItemNodeOrigin.x += updatedParentOrigin.x updatedAccessoryItemNodeOrigin.y += updatedParentOrigin.y updatedAccessoryItemNodeOrigin.y -= itemNode.bounds.origin.y + //updatedAccessoryItemNodeOrigin.y += itemNode.transitionOffset - let deltaHeight = itemNode.frame.size.height - nextItemNode.frame.size.height - + var deltaHeight = itemNode.frame.size.height - nextItemNode.frame.size.height + //deltaHeight = 0.0 nextAccessoryItemNode.animateTransitionOffset(CGPoint(x: 0.0, y: updatedAccessoryItemNodeOrigin.y - previousAccessoryItemNodeOrigin.y - deltaHeight), beginAt: currentTimestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem) + } } else { break @@ -2491,12 +2655,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if !didStealAccessoryNode { let accessoryNode = accessoryItem.node() itemNode.addSubnode(accessoryNode) - itemNode.accessoryItemNode = accessoryNode + itemNode.setAccessoryItemNode(accessoryNode, leftInset: leftInset, rightInset: rightInset) } } } else { itemNode.accessoryItemNode?.removeFromSupernode() - itemNode.accessoryItemNode = nil + itemNode.setAccessoryItemNode(nil, leftInset: leftInset, rightInset: rightInset) } } @@ -2597,9 +2761,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if node.index == nil && node.apparentHeight <= CGFloat.ulpOfOne { self.removeItemNodeAtIndex(i) if useBackgroundDeallocation { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: node) + assertionFailure() + //ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: node) } else { - ASPerformMainThreadDeallocation(node) + //ASPerformMainThreadDeallocation(node) } } else { i += 1 @@ -2740,6 +2905,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { offsetRanges.offset(IndexRange(first: index + 1, last: Int.max), offset: apparentHeightDelta) } + + if let accessoryItemNode = itemNode.accessoryItemNode { + itemNode.layoutAccessoryItemNode(accessoryItemNode, leftInset: self.insets.left, rightInset: self.insets.right) + } } if itemNode.index == nil && updatedApparentHeight <= CGFloat.ulpOfOne { @@ -2786,6 +2955,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } override open func touchesBegan(_ touches: Set, with event: UIEvent?) { + let touchesPosition = touches.first!.location(in: self.view) + + if let index = self.itemIndexAtPoint(touchesPosition) { + for i in 0 ..< self.itemNodes.count { + if self.itemNodes[i].preventsTouchesToOtherItems { + if index != self.itemNodes[i].index { + self.itemNodes[i].touchesToOtherItemsPrevented() + return + } + break + } + } + } + let offset = self.visibleContentOffset() switch offset { case let .known(value) where value <= 10.0: @@ -2794,27 +2977,57 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.beganTrackingAtTopOrigin = false } - self.touchesPosition = touches.first!.location(in: self.view) + self.touchesPosition = touchesPosition self.selectionTouchLocation = touches.first!.location(in: self.view) self.selectionTouchDelayTimer?.invalidate() + self.selectionLongTapDelayTimer?.invalidate() + self.selectionLongTapDelayTimer = nil let timer = Timer(timeInterval: 0.08, target: ListViewTimerProxy { [weak self] in - if let strongSelf = self , strongSelf.selectionTouchLocation != nil { + if let strongSelf = self, strongSelf.selectionTouchLocation != nil { strongSelf.clearHighlightAnimated(false) if let index = strongSelf.itemIndexAtPoint(strongSelf.touchesPosition) { - if strongSelf.items[index].selectable { + var canBeSelectedOrLongTapped = false + for itemNode in strongSelf.itemNodes { + if itemNode.index == index && (strongSelf.items[index].selectable && itemNode.canBeSelected) || itemNode.canBeLongTapped { + canBeSelectedOrLongTapped = true + } + } + + if canBeSelectedOrLongTapped { strongSelf.highlightedItemIndex = index for itemNode in strongSelf.itemNodes { if itemNode.index == index && itemNode.canBeSelected { - if true { //!(itemNode.hitTest(CGPoint(x: strongSelf.touchesPosition.x - itemNode.frame.minX, y: strongSelf.touchesPosition.y - itemNode.frame.minY), with: event) is UIControl) { + if true { if !itemNode.isLayerBacked { strongSelf.view.bringSubview(toFront: itemNode.view) for (_, headerNode) in strongSelf.itemHeaderNodes { strongSelf.view.bringSubview(toFront: headerNode.view) } } - itemNode.setHighlighted(true, animated: false) + let itemNodeFrame = itemNode.frame + let itemNodeBounds = itemNode.bounds + if strongSelf.items[index].selectable { + itemNode.setHighlighted(true, at: strongSelf.touchesPosition.offsetBy(dx: -itemNodeFrame.minX + itemNodeBounds.minX, dy: -itemNodeFrame.minY + itemNodeBounds.minY), animated: false) + } + + if itemNode.canBeLongTapped { + let timer = Timer(timeInterval: 0.3, target: ListViewTimerProxy { + if let strongSelf = self, strongSelf.highlightedItemIndex == index { + for itemNode in strongSelf.itemNodes { + if itemNode.index == index && itemNode.canBeLongTapped { + itemNode.longTapped() + strongSelf.clearHighlightAnimated(true) + strongSelf.selectionTouchLocation = nil + break + } + } + } + }, selector: #selector(ListViewTimerProxy.timerEvent), userInfo: nil, repeats: false) + strongSelf.selectionLongTapDelayTimer = timer + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) + } } break } @@ -2828,14 +3041,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel super.touchesBegan(touches, with: event) - self.updateScroller() + self.updateScroller(transition: .immediate) } public func clearHighlightAnimated(_ animated: Bool) { if let highlightedItemIndex = self.highlightedItemIndex { for itemNode in self.itemNodes { if itemNode.index == highlightedItemIndex { - itemNode.setHighlighted(false, animated: animated) + itemNode.setHighlighted(false, at: CGPoint(), animated: animated) break } } @@ -2845,7 +3058,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private func itemIndexAtPoint(_ point: CGPoint) -> Int? { for itemNode in self.itemNodes { - if itemNode.apparentFrame.contains(point) { + if itemNode.apparentContentFrame.contains(point) { return itemNode.index } } @@ -2891,7 +3104,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if distance.x * distance.x + distance.y * distance.y > maxMovementDistance * maxMovementDistance { self.selectionTouchLocation = nil self.selectionTouchDelayTimer?.invalidate() + self.selectionLongTapDelayTimer?.invalidate() self.selectionTouchDelayTimer = nil + self.selectionLongTapDelayTimer = nil self.clearHighlightAnimated(false) } } @@ -2918,7 +3133,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.view.bringSubview(toFront: headerNode.view) } } - itemNode.setHighlighted(true, animated: false) + let itemNodeFrame = itemNode.frame + itemNode.setHighlighted(true, at: selectionTouchLocation.offsetBy(dx: -itemNodeFrame.minX, dy: -itemNodeFrame.minY), animated: false) } else { self.highlightedItemIndex = nil } @@ -2941,6 +3157,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.selectionTouchLocation = nil self.selectionTouchDelayTimer?.invalidate() self.selectionTouchDelayTimer = nil + self.selectionLongTapDelayTimer?.invalidate() + self.selectionLongTapDelayTimer = nil self.clearHighlightAnimated(false) super.touchesCancelled(touches, with: event) diff --git a/Display/ListViewAccessoryItemNode.swift b/Display/ListViewAccessoryItemNode.swift index baeccbac18..1ac1354c09 100644 --- a/Display/ListViewAccessoryItemNode.swift +++ b/Display/ListViewAccessoryItemNode.swift @@ -1,5 +1,8 @@ import Foundation -import AsyncDisplayKit +#if os(macOS) +#else + import AsyncDisplayKit +#endif open class ListViewAccessoryItemNode: ASDisplayNode { var transitionOffset: CGPoint = CGPoint() { @@ -37,4 +40,13 @@ open class ListViewAccessoryItemNode: ASDisplayNode { return false } + + override open func layout() { + super.layout() + + self.updateLayout(size: self.bounds.size, leftInset: 0.0, rightInset: 0.0) + } + + open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + } } diff --git a/Display/ListViewAnimation.swift b/Display/ListViewAnimation.swift index 0d1a84768b..cf0ea9c09e 100644 --- a/Display/ListViewAnimation.swift +++ b/Display/ListViewAnimation.swift @@ -91,6 +91,7 @@ public let listViewAnimationCurveLinear: (CGFloat) -> CGFloat = { t in return t } +#if os(iOS) public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIViewAnimationOptions) -> (CGFloat) -> CGFloat { if animationOptions.rawValue == UInt(7 << 16) { return listViewAnimationCurveSystem @@ -98,6 +99,7 @@ public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIViewA return listViewAnimationCurveLinear } } +#endif public final class ListViewAnimation { let from: Interpolatable diff --git a/Display/ListViewFloatingHeaderNode.swift b/Display/ListViewFloatingHeaderNode.swift new file mode 100644 index 0000000000..ad2e7bdb5c --- /dev/null +++ b/Display/ListViewFloatingHeaderNode.swift @@ -0,0 +1,8 @@ +import Foundation +import AsyncDisplayKit + +open class ListViewFloatingHeaderNode: ASDisplayNode { + open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + return 0.0 + } +} diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index cf1fc977dc..8cf6609a95 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -1,5 +1,9 @@ import Foundation +#if os(macOS) +import SwiftSignalKitMac +#else import SwiftSignalKit +#endif public enum ListViewCenterScrollPositionOverflow { case top diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index 5b0f1a2e58..3e83961725 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -1,5 +1,9 @@ import Foundation +#if os(macOS) +import SwiftSignalKitMac +#else import SwiftSignalKit +#endif public enum ListViewItemUpdateAnimation { case None @@ -29,8 +33,8 @@ public struct ListViewItemConfigureNodeFlags: OptionSet { } public protocol ListViewItem { - func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) - func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) var accessoryItem: ListViewAccessoryItem? { get } var headerAccessoryItem: ListViewAccessoryItem? { get } diff --git a/Display/ListViewItemHeader.swift b/Display/ListViewItemHeader.swift index ec7e2d9419..db5a74e35f 100644 --- a/Display/ListViewItemHeader.swift +++ b/Display/ListViewItemHeader.swift @@ -1,5 +1,7 @@ import Foundation +#if !os(macOS) import AsyncDisplayKit +#endif public enum ListViewItemHeaderStickDirection { case top @@ -33,21 +35,6 @@ open class ListViewItemHeaderNode: ASDisplayNode { self.isFlashingOnScrolling = isFlashingOnScrolling self.updateFlashingOnScrolling(isFlashingOnScrolling, animated: animated) } - /*if self.isFlashing { - if self.alpha.isZero { - self.alpha = 1.0 - if animated { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - } - } - } else { - if !self.alpha.isZero { - self.alpha = 0.0 - if animated { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - } - }*/ } open func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) { @@ -139,4 +126,24 @@ open class ListViewItemHeaderNode: ASDisplayNode { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false) self.layer.animateScale(from: 1.0, to: 0.2, duration: duration, removeOnCompletion: false) } + + private var cachedLayout: (CGSize, CGFloat, CGFloat)? + + func updateLayoutInternal(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + var update = false + if let cachedLayout = self.cachedLayout { + if cachedLayout.0 != size || cachedLayout.1 != leftInset || cachedLayout.2 != rightInset { + update = true + } + } else { + update = true + } + if update { + self.cachedLayout = (size, leftInset, rightInset) + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset) + } + } + + open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + } } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index ebc762c643..e353a36b26 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -1,6 +1,10 @@ import Foundation +#if os(macOS) +import SwiftSignalKitMac +#else import AsyncDisplayKit import SwiftSignalKit +#endif var testSpringFrictionLimits: (CGFloat, CGFloat) = (3.0, 60.0) var testSpringFriction: CGFloat = 31.8211269378662 @@ -27,12 +31,6 @@ struct ListViewItemSpring { } } -private class ListViewItemView: UIView { - /*override class var layerClass: AnyClass { - return ASTransformLayer.self - }*/ -} - public struct ListViewItemNodeLayout { public let contentSize: CGSize public let insets: UIEdgeInsets @@ -58,15 +56,28 @@ public enum ListViewItemNodeVisibility { case visible } +public struct ListViewItemLayoutParams { + public let width: CGFloat + public let leftInset: CGFloat + public let rightInset: CGFloat + + public init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat) { + self.width = width + self.leftInset = leftInset + self.rightInset = rightInset + } +} + open class ListViewItemNode: ASDisplayNode { let rotated: Bool final var index: Int? - public final var accessoryItemNode: ListViewAccessoryItemNode? { - didSet { - if let accessoryItemNode = self.accessoryItemNode { - self.layoutAccessoryItemNode(accessoryItemNode) - } + public private(set) var accessoryItemNode: ListViewAccessoryItemNode? + + func setAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode?, leftInset: CGFloat, rightInset: CGFloat) { + self.accessoryItemNode = accessoryItemNode + if let accessoryItemNode = accessoryItemNode { + self.layoutAccessoryItemNode(accessoryItemNode, leftInset: leftInset, rightInset: rightInset) } } @@ -95,18 +106,27 @@ open class ListViewItemNode: ASDisplayNode { return true } + open var canBeLongTapped: Bool { + return false + } + + open var preventsTouchesToOtherItems: Bool { + return false + } + + open func touchesToOtherItemsPrevented() { + + } + + open func longTapped() { + } + public final var insets: UIEdgeInsets = UIEdgeInsets() { didSet { let effectiveInsets = self.insets self.frame = CGRect(origin: self.frame.origin, size: CGSize(width: self.contentSize.width, height: self.contentSize.height + effectiveInsets.top + effectiveInsets.bottom)) let bounds = self.bounds self.bounds = CGRect(origin: CGPoint(x: bounds.origin.x, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: bounds.size) - - if oldValue != self.insets { - if let accessoryItemNode = self.accessoryItemNode { - self.layoutAccessoryItemNode(accessoryItemNode) - } - } } } @@ -167,22 +187,6 @@ open class ListViewItemNode: ASDisplayNode { self.rotated = rotated - //super.init() - - //self.layerBacked = layerBacked - - /*if layerBacked { - super.init(layerBlock: { - return ASTransformLayer() - }) - } else { - super.init() - - self.setViewBlock({ - return ListViewItemView() - }) - }*/ - if seeThrough { if (layerBacked) { super.init() @@ -203,12 +207,6 @@ open class ListViewItemNode: ASDisplayNode { } } - /*deinit { - if Thread.isMainThread { - print("deallocating on main thread") - } - }*/ - var apparentHeight: CGFloat = 0.0 private var _bounds: CGRect = CGRect() private var _position: CGPoint = CGPoint() @@ -226,9 +224,6 @@ open class ListViewItemNode: ASDisplayNode { self._contentSize = CGSize(width: value.size.width, height: value.size.height - effectiveInsets.top - effectiveInsets.bottom) if previousSize != value.size { - if let accessoryItemNode = self.accessoryItemNode { - self.layoutAccessoryItemNode(accessoryItemNode) - } if let headerAccessoryItemNode = self.headerAccessoryItemNode { self.layoutHeaderAccessoryItemNode(headerAccessoryItemNode) } @@ -248,9 +243,6 @@ open class ListViewItemNode: ASDisplayNode { self._contentSize = CGSize(width: value.size.width, height: value.size.height - effectiveInsets.top - effectiveInsets.bottom) if previousSize != value.size { - if let accessoryItemNode = self.accessoryItemNode { - self.layoutAccessoryItemNode(accessoryItemNode) - } if let headerAccessoryItemNode = self.headerAccessoryItemNode { self.layoutHeaderAccessoryItemNode(headerAccessoryItemNode) } @@ -279,13 +271,21 @@ open class ListViewItemNode: ASDisplayNode { return frame } + public final var apparentContentFrame: CGRect { + var frame = self.frame + let insets = self.insets + frame.origin.y += insets.top + frame.size.height = self.apparentHeight - insets.top - insets.bottom + return frame + } + public final var apparentBounds: CGRect { var bounds = self.bounds bounds.size.height = self.apparentHeight return bounds } - open func layoutAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + open func layoutAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode, leftInset: CGFloat, rightInset: CGFloat) { } open func layoutHeaderAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { @@ -371,7 +371,7 @@ open class ListViewItemNode: ASDisplayNode { return continueAnimations } - open func layoutForWidth(_ width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + open func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { } public func animationForKey(_ key: String) -> ListViewAnimation? { @@ -417,6 +417,19 @@ open class ListViewItemNode: ASDisplayNode { self.setAnimationForKey("insets", animation: animation) } + public func addHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) { + let animation = ListViewAnimation(from: self.bounds.height, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in + if let strongSelf = self { + let frame = strongSelf.frame + strongSelf.frame = CGRect(origin: frame.origin, size: CGSize(width: frame.width, height: currentValue)) + if let update = update { + update(progress, currentValue) + } + } + }) + self.setAnimationForKey("height", animation: animation) + } + public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) { let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in if let strongSelf = self { @@ -468,7 +481,7 @@ open class ListViewItemNode: ASDisplayNode { open func animateRemoved(_ currentTimestamp: Double, duration: Double) { } - open func setHighlighted(_ highlighted: Bool, animated: Bool) { + open func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { } open func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { diff --git a/Display/ListViewOverscrollBackgroundNode.swift b/Display/ListViewOverscrollBackgroundNode.swift new file mode 100644 index 0000000000..e44586eb3d --- /dev/null +++ b/Display/ListViewOverscrollBackgroundNode.swift @@ -0,0 +1,31 @@ +import Foundation +#if os(macOS) +#else +import AsyncDisplayKit +#endif + +final class ListViewOverscrollBackgroundNode: ASDisplayNode { + private let backgroundNode: ASDisplayNode + + var color: UIColor { + didSet { + self.backgroundNode.backgroundColor = color + } + } + + init(color: UIColor) { + self.color = color + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = color + self.backgroundNode.isLayerBacked = true + + super.init() + + self.addSubnode(self.backgroundNode) + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + } +} diff --git a/Display/ListViewScroller.swift b/Display/ListViewScroller.swift index 2d1292ff41..c853d1fd0c 100644 --- a/Display/ListViewScroller.swift +++ b/Display/ListViewScroller.swift @@ -1,4 +1,7 @@ +#if os(macOS) +#else import UIKit +#endif class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { override init(frame: CGRect) { diff --git a/Display/ListViewScrollerAppkit.swift b/Display/ListViewScrollerAppkit.swift deleted file mode 100644 index 95e31a3df5..0000000000 --- a/Display/ListViewScrollerAppkit.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation -import AppKit - -class ListViewScroller: CALayer { -} diff --git a/Display/MergedLayoutEvents.swift b/Display/MergedLayoutEvents.swift deleted file mode 100644 index 575f4cea6a..0000000000 --- a/Display/MergedLayoutEvents.swift +++ /dev/null @@ -1,9 +0,0 @@ -import UIKit - -protocol MergeableLayoutEvent { - -} - -final class MergedLayoutEvents { - -} diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index dd1bd8009b..cd0288008b 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -36,6 +36,16 @@ private class WindowRootViewController: UIViewController { } } + var preferNavigationUIHidden: Bool = false { + didSet { + if oldValue != self.preferNavigationUIHidden { + if #available(iOSApplicationExtension 11.0, *) { + self.setNeedsUpdateOfHomeIndicatorAutoHidden() + } + } + } + } + override var preferredStatusBarStyle: UIStatusBarStyle { return .default } @@ -51,6 +61,10 @@ private class WindowRootViewController: UIViewController { override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge { return self.gestureEdges } + + override func prefersHomeIndicatorAutoHidden() -> Bool { + return self.preferNavigationUIHidden + } } private final class NativeWindow: UIWindow, WindowHost { @@ -62,6 +76,8 @@ private final class NativeWindow: UIWindow, WindowHost { var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? var presentNativeImpl: ((UIViewController) -> Void)? var invalidateDeferScreenEdgeGestureImpl: (() -> Void)? + var invalidatePreferNavigationUIHiddenImpl: (() -> Void)? + var cancelInteractiveKeyboardGesturesImpl: (() -> Void)? private var frameTransition: ContainedViewLayoutTransition? @@ -98,6 +114,20 @@ private final class NativeWindow: UIWindow, WindowHost { } } + override init(frame: CGRect) { + super.init(frame: frame) + + if let gestureRecognizers = self.gestureRecognizers { + for recognizer in gestureRecognizers { + recognizer.delaysTouchesBegan = false + } + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func layoutSubviews() { super.layoutSubviews() @@ -147,6 +177,14 @@ private final class NativeWindow: UIWindow, WindowHost { func invalidateDeferScreenEdgeGestures() { self.invalidateDeferScreenEdgeGestureImpl?() } + + func invalidatePreferNavigationUIHidden() { + self.invalidatePreferNavigationUIHiddenImpl?() + } + + func cancelInteractiveKeyboardGestures() { + self.cancelInteractiveKeyboardGesturesImpl?() + } } public func nativeWindowHostView() -> WindowHostView { @@ -164,6 +202,8 @@ public func nativeWindowHostView() -> WindowHostView { rootViewController.orientations = orientations }, updateDeferScreenEdgeGestures: { edges in rootViewController.gestureEdges = edges + }, updatePreferNavigationUIHidden: { value in + rootViewController.preferNavigationUIHidden = value }) window.updateSize = { [weak hostView] size in @@ -198,6 +238,14 @@ public func nativeWindowHostView() -> WindowHostView { return hostView?.invalidateDeferScreenEdgeGesture?() } + window.invalidatePreferNavigationUIHiddenImpl = { [weak hostView] in + return hostView?.invalidatePreferNavigationUIHidden?() + } + + window.cancelInteractiveKeyboardGesturesImpl = { [weak hostView] in + hostView?.cancelInteractiveKeyboardGestures?() + } + rootViewController.presentController = { [weak hostView] controller, level, animated, completion in if let strongSelf = hostView { strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 6249b98b6c..cdb421e869 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -61,6 +61,9 @@ private func backArrowImage(color: UIColor) -> UIImage? { open class NavigationBar: ASDisplayNode { private var theme: NavigationBarTheme + private var validLayout: (CGSize, CGFloat, CGFloat)? + private var requestedLayout: Bool = false + var backPressed: () -> () = { } private var collapsed: Bool { @@ -156,7 +159,7 @@ open class NavigationBar: ASDisplayNode { strongSelf.updateLeftButton(animated: animated) strongSelf.invalidateCalculatedLayout() - strongSelf.setNeedsLayout() + strongSelf.requestLayout() } } @@ -177,7 +180,7 @@ open class NavigationBar: ASDisplayNode { strongSelf.updateRightButton(animated: animated) strongSelf.invalidateCalculatedLayout() - strongSelf.setNeedsLayout() + strongSelf.requestLayout() } } @@ -196,6 +199,7 @@ open class NavigationBar: ASDisplayNode { self.updateRightButton(animated: false) } self.invalidateCalculatedLayout() + self.requestLayout() } } @@ -211,7 +215,7 @@ open class NavigationBar: ASDisplayNode { } self.invalidateCalculatedLayout() - self.setNeedsLayout() + self.requestLayout() } } @@ -226,7 +230,7 @@ open class NavigationBar: ASDisplayNode { } self.invalidateCalculatedLayout() - self.setNeedsLayout() + self.requestLayout() } } @@ -261,6 +265,7 @@ open class NavigationBar: ASDisplayNode { strongSelf.backButtonNode.text = previousItem.title ?? "" } strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() } } @@ -272,12 +277,14 @@ open class NavigationBar: ASDisplayNode { strongSelf.backButtonNode.text = previousItem.title ?? "" } strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() } } } self.updateLeftButton(animated: false) self.invalidateCalculatedLayout() + self.requestLayout() } } @@ -288,13 +295,13 @@ open class NavigationBar: ASDisplayNode { self.badgeNode.isHidden = actualText.isEmpty self.invalidateCalculatedLayout() - self.setNeedsLayout() + self.requestLayout() } } private func updateLeftButton(animated: Bool) { if let item = self.item { - if let leftBarButtonItem = item.leftBarButtonItem { + if let leftBarButtonItem = item.leftBarButtonItem, !leftBarButtonItem.backButtonAppearance { if animated { if self.leftButtonNode.view.superview != nil { if let snapshotView = self.leftButtonNode.view.snapshotContentTree() { @@ -365,13 +372,19 @@ open class NavigationBar: ASDisplayNode { } self.leftButtonNode.removeFromSupernode() - if let previousItem = self.previousItem { + var backTitle: String? + if let leftBarButtonItem = item.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { + backTitle = leftBarButtonItem.title + } else if let previousItem = self.previousItem { if let backBarButtonItem = previousItem.backBarButtonItem { - self.backButtonNode.text = backBarButtonItem.title ?? "Back" + backTitle = backBarButtonItem.title ?? "Back" } else { - self.backButtonNode.text = previousItem.title ?? "Back" + backTitle = previousItem.title ?? "Back" } - + } + + if let backTitle = backTitle { + self.backButtonNode.text = backTitle if self.backButtonNode.supernode == nil { self.clippingNode.addSubnode(self.backButtonNode) self.clippingNode.addSubnode(self.backButtonArrow) @@ -500,6 +513,7 @@ open class NavigationBar: ASDisplayNode { } } + self.requestedLayout = true self.layout() } } @@ -557,7 +571,13 @@ open class NavigationBar: ASDisplayNode { } } self.backButtonNode.pressed = { [weak self] in - self?.backPressed() + if let strongSelf = self { + if let leftBarButtonItem = strongSelf.item?.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { + leftBarButtonItem.performActionOnTarget() + } else { + strongSelf.backPressed() + } + } } self.leftButtonNode.pressed = { [weak self] in @@ -592,25 +612,41 @@ open class NavigationBar: ASDisplayNode { } } - open override func layout() { - let size = self.bounds.size + private func requestLayout() { + self.requestedLayout = true + self.setNeedsLayout() + } + + override open func layout() { + super.layout() - let leftButtonInset: CGFloat = 16.0 - let backButtonInset: CGFloat = 27.0 + if let validLayout = self.validLayout, self.requestedLayout { + self.requestedLayout = false + self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, transition: .immediate) + } + } + + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, leftInset, rightInset) - self.clippingNode.frame = CGRect(origin: CGPoint(), size: size) - self.contentNode?.frame = CGRect(origin: CGPoint(), size: size) + let leftButtonInset: CGFloat = leftInset + 16.0 + let backButtonInset: CGFloat = leftInset + 27.0 - self.stripeNode.frame = CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel) + transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: size)) + if let contentNode = self.contentNode { + transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: size.width - leftInset - rightInset, height: size.height))) + } + + transition.updateFrame(node: self.stripeNode, frame: CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel)) let nominalHeight: CGFloat = self.collapsed ? 32.0 : 44.0 let contentVerticalOrigin = size.height - nominalHeight - var leftTitleInset: CGFloat = 8.0 - var rightTitleInset: CGFloat = 8.0 + var leftTitleInset: CGFloat = leftInset + 4.0 + var rightTitleInset: CGFloat = rightInset + 4.0 if self.backButtonNode.supernode != nil { let backButtonSize = self.backButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) - leftTitleInset += backButtonSize.width + backButtonInset + 8.0 + 8.0 + leftTitleInset += backButtonSize.width + backButtonInset + 4.0 + 4.0 let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5 self.backButtonNode.hitTestSlop = UIEdgeInsetsMake(-topHitTestSlop, -27.0, -topHitTestSlop, -8.0) @@ -636,21 +672,21 @@ open class NavigationBar: ASDisplayNode { transitionTitleNode.alpha = progress * progress } - self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0 - progress * size.width, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + self.backButtonArrow.frame = CGRect(origin: CGPoint(x: leftInset + 8.0 - progress * size.width, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) self.backButtonArrow.alpha = max(0.0, 1.0 - progress * 1.3) self.badgeNode.alpha = max(0.0, 1.0 - progress * 1.3) case .bottom: self.backButtonNode.alpha = 1.0 self.backButtonNode.frame = CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) self.backButtonArrow.alpha = 1.0 - self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + self.backButtonArrow.frame = CGRect(origin: CGPoint(x: leftInset + 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) self.badgeNode.alpha = 1.0 } } else { self.backButtonNode.alpha = 1.0 self.backButtonNode.frame = CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) self.backButtonArrow.alpha = 1.0 - self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + self.backButtonArrow.frame = CGRect(origin: CGPoint(x: leftInset + 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) self.badgeNode.alpha = 1.0 } } else if self.leftButtonNode.supernode != nil { @@ -689,8 +725,8 @@ open class NavigationBar: ASDisplayNode { } if let transitionBackArrowNode = self.transitionBackArrowNode { - let initialX: CGFloat = 8.0 + size.width * 0.3 - let finalX: CGFloat = 8.0 + let initialX: CGFloat = leftInset + 8.0 + size.width * 0.3 + let finalX: CGFloat = leftInset + 8.0 transitionBackArrowNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) transitionBackArrowNode.alpha = max(0.0, 1.0 - progress * 1.3) @@ -739,7 +775,7 @@ open class NavigationBar: ASDisplayNode { } if let titleView = self.titleView { - let titleSize = CGSize(width: max(1.0, size.width - leftTitleInset - leftTitleInset), height: nominalHeight) + let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight) titleView.frame = CGRect(origin: CGPoint(x: leftTitleInset, y: contentVerticalOrigin), size: titleSize) if let transitionState = self.transitionState, let otherNavigationBar = transitionState.navigationBar { @@ -859,9 +895,10 @@ open class NavigationBar: ASDisplayNode { } if !self.bounds.size.width.isZero { + self.requestedLayout = true self.layout() } else { - self.setNeedsLayout() + self.requestLayout() } } else if self.clippingNode.alpha.isZero { self.clippingNode.alpha = 1.0 diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index e0fedbdb29..531ccdc445 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -73,7 +73,7 @@ open class NavigationController: UINavigationController, ContainableController, self.loadView() } self.containerLayout = layout - self.view.frame = CGRect(origin: self.view.frame.origin, size: layout.size) + transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging) @@ -81,7 +81,7 @@ open class NavigationController: UINavigationController, ContainableController, if let topViewController = topViewController as? ContainableController { topViewController.containerLayoutUpdated(containedLayout, transition: transition) } else { - topViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) + transition.updateFrame(view: topViewController.view, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)) } } @@ -89,7 +89,7 @@ open class NavigationController: UINavigationController, ContainableController, if let presentedViewController = presentedViewController as? ContainableController { presentedViewController.containerLayoutUpdated(containedLayout, transition: transition) } else { - presentedViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) + transition.updateFrame(view: presentedViewController.view, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)) } } @@ -109,6 +109,7 @@ open class NavigationController: UINavigationController, ContainableController, let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) panRecognizer.delegate = self + panRecognizer.delaysTouchesBegan = false panRecognizer.cancelsTouchesInView = true self.view.addGestureRecognizer(panRecognizer) @@ -349,9 +350,9 @@ open class NavigationController: UINavigationController, ContainableController, } } - bottomController.viewWillDisappear(true) + bottomController.viewWillAppear(true) let bottomView = bottomController.view! - topController.viewWillAppear(true) + topController.viewWillDisappear(true) let topView = topController.view! let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index 894114ab9d..0cb39d4d89 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -120,7 +120,7 @@ public final class StatusBar: ASDisplayNode { self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } - func updateState(statusBar: UIView?, inCallText: String?, animated: Bool) { + func updateState(statusBar: UIView?, withSafeInsets: Bool, inCallText: String?, animated: Bool) { if let statusBar = statusBar { self.removeProxyNodeScheduled = false let resolvedStyle: StatusBarStyle @@ -166,7 +166,9 @@ public final class StatusBar: ASDisplayNode { if (resolvedInCallText != nil) != (self.inCallText != nil) { if let _ = resolvedInCallText { - self.addSubnode(self.inCallLabel) + if !withSafeInsets { + self.addSubnode(self.inCallLabel) + } addInCallAnimation(self.inCallLabel.layer) self.inCallBackgroundNode.layer.backgroundColor = inCallBackgroundColor.cgColor diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 6f12a61d1e..4ab5f9483e 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -79,13 +79,13 @@ class StatusBarManager { self.host = host } - func updateState(surfaces: [StatusBarSurface], forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { + func updateState(surfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { let previousSurfaces = self.surfaces self.surfaces = surfaces - self.updateSurfaces(previousSurfaces, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated) + self.updateSurfaces(previousSurfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated) } - private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { + private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { let statusBarFrame = self.host.statusBarFrame guard let statusBarView = self.host.statusBarView else { return @@ -197,7 +197,7 @@ class StatusBarManager { for surface in previousSurfaces { for statusBar in surface.statusBars { if !visibleStatusBars.contains(where: {$0 === statusBar}) { - statusBar.updateState(statusBar: nil, inCallText: forceInCallStatusBarText, animated: animated) + statusBar.updateState(statusBar: nil, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated) } } } @@ -206,13 +206,13 @@ class StatusBarManager { for statusBar in surface.statusBars { statusBar.inCallNavigate = self.inCallNavigate if !visibleStatusBars.contains(where: {$0 === statusBar}) { - statusBar.updateState(statusBar: nil, inCallText: forceInCallStatusBarText, animated: animated) + statusBar.updateState(statusBar: nil, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated) } } } for statusBar in visibleStatusBars { - statusBar.updateState(statusBar: statusBarView, inCallText: forceInCallStatusBarText, animated: animated) + statusBar.updateState(statusBar: statusBarView, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated) } if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows { diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 2786088fe0..b6750b07fb 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -306,7 +306,7 @@ class StatusBarProxyNode: ASDisplayNode { self.updateItems() self.timer = Timer(timeInterval: 5.0, target: StatusBarProxyNodeTimerTarget { [weak self] in self?.updateItems() - }, selector: #selector(StatusBarProxyNodeTimerTarget.tick), userInfo: nil, repeats: true) + }, selector: #selector(StatusBarProxyNodeTimerTarget.tick), userInfo: nil, repeats: true) RunLoop.main.add(self.timer!, forMode: .commonModes) } else { self.timer?.invalidate() diff --git a/Display/SystemContainedControllerTransitionCoordinator.swift b/Display/SystemContainedControllerTransitionCoordinator.swift deleted file mode 100644 index b861536e24..0000000000 --- a/Display/SystemContainedControllerTransitionCoordinator.swift +++ /dev/null @@ -1,73 +0,0 @@ -import UIKit - -final class SystemContainedControllerTransitionCoordinator: NSObject, UIViewControllerTransitionCoordinator { - public var isAnimated: Bool { - return false - } - - public var presentationStyle: UIModalPresentationStyle { - return .fullScreen - } - - public var initiallyInteractive: Bool { - return false - } - - public let isInterruptible: Bool = false - - public var isInteractive: Bool { - return false - } - - public var isCancelled: Bool { - return false - } - - public var transitionDuration: TimeInterval { - return 0.6 - } - - public var percentComplete: CGFloat { - return 0.0 - } - - public var completionVelocity: CGFloat { - return 0.0 - } - - public var completionCurve: UIViewAnimationCurve { - return .easeInOut - } - - public func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController? { - return nil - } - - public func view(forKey key: UITransitionContextViewKey) -> UIView? { - return nil - } - - public var containerView: UIView { - return UIView() - } - - public var targetTransform: CGAffineTransform { - return CGAffineTransform.identity - } - - public func animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { - return false - } - - public func animateAlongsideTransition(in view: UIView?, animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { - return false - } - - public func notifyWhenInteractionEnds(_ handler: @escaping (UIViewControllerTransitionCoordinatorContext) -> ()) { - - } - - public func notifyWhenInteractionChanges(_ handler: @escaping (UIViewControllerTransitionCoordinatorContext) -> ()) { - - } -} diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index 0b7bed48e5..f4e5d3b420 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -36,11 +36,16 @@ final class TabBarControllerNode: ASDisplayNode { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { let update = { - let tabBarHeight = 49.0 + layout.insets(options: []).bottom - self.tabBarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight)) - if self.tabBarNode.isNodeLoaded { - self.tabBarNode.layout() + let tabBarHeight: CGFloat + let bottomInset: CGFloat = layout.insets(options: []).bottom + if !layout.safeInsets.left.isZero { + tabBarHeight = 34.0 + bottomInset + } else { + tabBarHeight = 49.0 + bottomInset } + + transition.updateFrame(node: self.tabBarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight))) + self.tabBarNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, transition: transition) } switch transition { diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 087e614ce7..1b19c3152a 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -3,18 +3,30 @@ import UIKit import AsyncDisplayKit private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale -private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor) -> UIImage? { - let font = Font.medium(10.0) +private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor, horizontal: Bool) -> UIImage? { + let font = horizontal ? Font.regular(13.0) : Font.medium(10.0) let titleSize = (title as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [NSAttributedStringKey.font: font], context: nil).size let imageSize: CGSize if let image = image { - imageSize = image.size + if horizontal { + let factor: CGFloat = 0.8 + imageSize = CGSize(width: floor(image.size.width * factor), height: floor(image.size.height * factor)) + } else { + imageSize = image.size + } } else { imageSize = CGSize() } - let size = CGSize(width: max(ceil(titleSize.width), imageSize.width), height: 45.0) + let horizontalSpacing: CGFloat = 4.0 + + let size: CGSize + if horizontal { + size = CGSize(width: ceil(titleSize.width) + horizontalSpacing + imageSize.width, height: 34.0) + } else { + size = CGSize(width: max(ceil(titleSize.width), imageSize.width), height: 45.0) + } UIGraphicsBeginImageContextWithOptions(size, true, 0.0) if let context = UIGraphicsGetCurrentContext() { @@ -22,19 +34,35 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: context.fill(CGRect(origin: CGPoint(), size: size)) if let image = image { - let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 1.0), size: imageSize) - context.saveGState() - context.translateBy(x: imageRect.midX, y: imageRect.midY) - context.scaleBy(x: 1.0, y: -1.0) - context.translateBy(x: -imageRect.midX, y: -imageRect.midY) - context.clip(to: imageRect, mask: image.cgImage!) - context.setFillColor(tintColor.cgColor) - context.fill(imageRect) - context.restoreGState() + if horizontal { + let imageRect = CGRect(origin: CGPoint(x: 0.0, y: floor((size.height - imageSize.height) / 2.0)), size: imageSize) + context.saveGState() + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) + context.clip(to: imageRect, mask: image.cgImage!) + context.setFillColor(tintColor.cgColor) + context.fill(imageRect) + context.restoreGState() + } else { + let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 1.0), size: imageSize) + context.saveGState() + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) + context.clip(to: imageRect, mask: image.cgImage!) + context.setFillColor(tintColor.cgColor) + context.fill(imageRect) + context.restoreGState() + } } } - (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) + if horizontal { + (title as NSString).draw(at: CGPoint(x: imageSize.width + horizontalSpacing, y: floor((size.height - titleSize.height) / 2.0) - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) + } else { + (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) + } let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() @@ -52,6 +80,7 @@ private final class TabBarNodeContainer { let updateSelectedImageListenerIndex: Int let imageNode: ASImageNode + let badgeContainerNode: ASDisplayNode let badgeBackgroundNode: ASImageNode let badgeTextNode: ASTextNode @@ -72,6 +101,9 @@ private final class TabBarNodeContainer { self.imageNode = imageNode + self.badgeContainerNode = ASDisplayNode() + self.badgeContainerNode.isLayerBacked = true + self.badgeBackgroundNode = ASImageNode() self.badgeBackgroundNode.isLayerBacked = true self.badgeBackgroundNode.displayWithoutProcessing = true @@ -82,6 +114,9 @@ private final class TabBarNodeContainer { self.badgeTextNode.isLayerBacked = true self.badgeTextNode.displaysAsynchronously = false + self.badgeContainerNode.addSubnode(self.badgeBackgroundNode) + self.badgeContainerNode.addSubnode(self.badgeTextNode) + self.badgeValue = item.badgeValue ?? "" self.updateBadgeListenerIndex = UITabBarItem_addSetBadgeListener(item, { value in updateBadge(value ?? "") @@ -122,11 +157,11 @@ class TabBarNode: ASDisplayNode { didSet { if self.selectedIndex != oldValue { if let oldValue = oldValue { - self.updateNodeImage(oldValue) + self.updateNodeImage(oldValue, layout: true) } if let selectedIndex = self.selectedIndex { - self.updateNodeImage(selectedIndex) + self.updateNodeImage(selectedIndex, layout: true) } } } @@ -135,6 +170,8 @@ class TabBarNode: ASDisplayNode { private let itemSelected: (Int) -> Void private var theme: TabBarControllerTheme + private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)? + private var horizontal: Bool = false private var badgeImage: UIImage @@ -175,7 +212,7 @@ class TabBarNode: ASDisplayNode { } for i in 0 ..< self.tabBarItems.count { - self.updateNodeImage(i) + self.updateNodeImage(i, layout: false) self.tabBarNodeContainers[i].badgeBackgroundNode.image = self.badgeImage } @@ -185,8 +222,7 @@ class TabBarNode: ASDisplayNode { private func reloadTabBarItems() { for node in self.tabBarNodeContainers { node.imageNode.removeFromSupernode() - node.badgeBackgroundNode.removeFromSupernode() - node.badgeTextNode.removeFromSupernode() + node.badgeContainerNode.removeFromSupernode() } var tabBarNodeContainers: [TabBarNodeContainer] = [] @@ -199,16 +235,16 @@ class TabBarNode: ASDisplayNode { let container = TabBarNodeContainer(item: item, imageNode: node, updateBadge: { [weak self] value in self?.updateNodeBadge(i, value: value) }, updateTitle: { [weak self] _, _ in - self?.updateNodeImage(i) + self?.updateNodeImage(i, layout: true) }, updateImage: { [weak self] _ in - self?.updateNodeImage(i) + self?.updateNodeImage(i, layout: true) }, updateSelectedImage: { [weak self] _ in - self?.updateNodeImage(i) + self?.updateNodeImage(i, layout: true) }) if let selectedIndex = self.selectedIndex, selectedIndex == i { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor) + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) } else { - node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor) + node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) } container.badgeBackgroundNode.image = self.badgeImage tabBarNodeContainers.append(container) @@ -216,8 +252,7 @@ class TabBarNode: ASDisplayNode { } for container in tabBarNodeContainers { - self.addSubnode(container.badgeBackgroundNode) - self.addSubnode(container.badgeTextNode) + self.addSubnode(container.badgeContainerNode) } self.tabBarNodeContainers = tabBarNodeContainers @@ -225,19 +260,21 @@ class TabBarNode: ASDisplayNode { self.setNeedsLayout() } - private func updateNodeImage(_ index: Int) { + private func updateNodeImage(_ index: Int, layout: Bool) { if index < self.tabBarNodeContainers.count && index < self.tabBarItems.count { let node = self.tabBarNodeContainers[index].imageNode let item = self.tabBarItems[index] let previousImage = node.image if let selectedIndex = self.selectedIndex, selectedIndex == index { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor) + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) } else { - node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor) + node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) } if previousImage?.size != node.image?.size { - self.layout() + if let validLayout = self.validLayout, layout { + self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, bottomInset: validLayout.3, transition: .immediate) + } } } } @@ -245,23 +282,33 @@ class TabBarNode: ASDisplayNode { private func updateNodeBadge(_ index: Int, value: String) { self.tabBarNodeContainers[index].badgeValue = value if self.tabBarNodeContainers[index].badgeValue != self.tabBarNodeContainers[index].appliedBadgeValue { - self.layout() + if let validLayout = self.validLayout { + self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, bottomInset: validLayout.3, transition: .immediate) + } } } private func updateNodeTitle(_ index: Int, value: String) { self.tabBarNodeContainers[index].titleValue = value if self.tabBarNodeContainers[index].titleValue != self.tabBarNodeContainers[index].appliedTitleValue { - self.layout() + if let validLayout = self.validLayout { + self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, bottomInset: validLayout.3, transition: .immediate) + } } } - override func layout() { - super.layout() + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, leftInset, rightInset, bottomInset) - let size = self.bounds.size + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -separatorHeight), size: CGSize(width: size.width, height: separatorHeight))) - self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -separatorHeight), size: CGSize(width: size.width, height: separatorHeight)) + let horizontal = !leftInset.isZero + if self.horizontal != horizontal { + self.horizontal = horizontal + for i in 0 ..< self.tabBarItems.count { + self.updateNodeImage(i, layout: false) + } + } if self.tabBarNodeContainers.count != 0 { let distanceBetweenNodes = size.width / CGFloat(self.tabBarNodeContainers.count) @@ -275,26 +322,33 @@ class TabBarNode: ASDisplayNode { let nodeSize = node.image?.size ?? CGSize() let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - nodeSize.width / 2.0) - node.frame = CGRect(origin: CGPoint(x: originX, y: 4.0), size: nodeSize) + transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: originX, y: 4.0), size: nodeSize)) if container.badgeValue != container.appliedBadgeValue { container.appliedBadgeValue = container.badgeValue if let badgeValue = container.badgeValue, !badgeValue.isEmpty { container.badgeTextNode.attributedText = NSAttributedString(string: badgeValue, font: badgeFont, textColor: self.theme.tabBarBadgeTextColor) - container.badgeBackgroundNode.isHidden = false - container.badgeTextNode.isHidden = false + container.badgeContainerNode.isHidden = false } else { - container.badgeBackgroundNode.isHidden = true - container.badgeTextNode.isHidden = true + container.badgeContainerNode.isHidden = true } } - if !container.badgeBackgroundNode.isHidden { + if !container.badgeContainerNode.isHidden { let badgeSize = container.badgeTextNode.measure(CGSize(width: 200.0, height: 100.0)) let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) - let backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 3.0 + node.frame.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) - container.badgeBackgroundNode.frame = backgroundFrame - container.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: 3.0), size: badgeSize) + let backgroundFrame: CGRect + if horizontal { + backgroundFrame = CGRect(origin: CGPoint(x: originX, y: 2.0), size: backgroundSize) + } else { + backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 3.0 + node.frame.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) + } + transition.updateFrame(node: container.badgeContainerNode, frame: backgroundFrame) + container.badgeBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size) + let scaleFactor: CGFloat = horizontal ? 0.8 : 1.0 + container.badgeContainerNode.subnodeTransform = CATransform3DMakeScale(scaleFactor, scaleFactor, 1.0) + + container.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundFrame.size.width - badgeSize.width) / 2.0), y: 1.0), size: badgeSize) } } } @@ -303,8 +357,11 @@ class TabBarNode: ASDisplayNode { override func touchesBegan(_ touches: Set, with event: UIEvent?) { super.touchesBegan(touches, with: event) - if let touch = touches.first { + if let touch = touches.first, let bottomInset = self.validLayout?.3 { let location = touch.location(in: self.view) + if location.y > self.bounds.size.height - bottomInset { + return + } var closestNode: (Int, CGFloat)? for i in 0 ..< self.tabBarNodeContainers.count { diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index 8bf98938ca..fce5275127 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -4,6 +4,7 @@ import AsyncDisplayKit public enum TextAlertActionType { case genericAction case defaultAction + case destructiveAction } public struct TextAlertAction { @@ -34,7 +35,15 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { super.init() self.titleNode.maximumNumberOfLines = 2 - self.setAttributedTitle(NSAttributedString(string: action.title, font: Font.regular(17.0), textColor: UIColor(rgb: 0x007ee5), paragraphAlignment: .center), for: []) + let font = Font.regular(17.0) + var color = UIColor(rgb: 0x007ee5) + switch action.type { + case .defaultAction, .genericAction: + break + case .destructiveAction: + color = UIColor(rgb: 0xff3b30) + } + self.setAttributedTitle(NSAttributedString(string: action.title, font: font, textColor: color, paragraphAlignment: .center), for: []) self.highligthedChanged = { [weak self] value in if let strongSelf = self { @@ -154,7 +163,8 @@ final class TextAlertContentNode: AlertContentNode { let resultSize: CGSize if let titleNode = titleNode, let titleSize = titleSize { - let contentWidth = max(max(titleSize.width, textSize.width), minActionsWidth) + var contentWidth = max(max(titleSize.width, textSize.width), minActionsWidth) + contentWidth = max(contentWidth, 150.0) let spacing: CGFloat = 6.0 let titleFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - titleSize.width) / 2.0), y: insets.top), size: titleSize) @@ -165,10 +175,13 @@ final class TextAlertContentNode: AlertContentNode { resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: titleSize.height + spacing + textSize.height + actionsHeight + insets.top + insets.bottom) } else { - let textFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: textSize) + var contentWidth = max(textSize.width, minActionsWidth) + contentWidth = max(contentWidth, 150.0) + + let textFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - textSize.width) / 2.0), y: insets.top), size: textSize) transition.updateFrame(node: self.textNode, frame: textFrame) - resultSize = CGSize(width: textSize.width + insets.left + insets.right, height: textSize.height + actionsHeight + insets.top + insets.bottom) + resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: textSize.height + actionsHeight + insets.top + insets.bottom) } self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)) diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index 09e212bddd..1041ec2a67 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -1,5 +1,7 @@ #import "UIKitUtils.h" +#import + #if TARGET_IPHONE_SIMULATOR UIKIT_EXTERN float UIAnimationDragCoefficient(); // UIKit private drag coeffient, use judiciously #endif @@ -19,14 +21,35 @@ UIKIT_EXTERN float UIAnimationDragCoefficient(); // UIKit private drag coeffient @interface CASpringAnimation () -- (float)_solveForInput:(float)arg1; - @end @implementation CASpringAnimation (AnimationUtils) - (CGFloat)valueAt:(CGFloat)t { - return [self _solveForInput:t]; + static dispatch_once_t onceToken; + static float (*impl)(id, float) = NULL; + static double (*dimpl)(id, double) = NULL; + dispatch_once(&onceToken, ^{ + Method method = class_getInstanceMethod([CASpringAnimation class], NSSelectorFromString([@"_" stringByAppendingString:@"solveForInput:"])); + if (method) { + const char *encoding = method_getTypeEncoding(method); + NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:encoding]; + const char *argType = [signature getArgumentTypeAtIndex:2]; + if (strncmp(argType, "f", 1) == 0) { + impl = (float (*)(id, float))method_getImplementation(method); + } else if (strncmp(argType, "d", 1) == 0) { + dimpl = (double (*)(id, double))method_getImplementation(method); + } + } + }); + if (impl) { + float result = impl(self, (float)t); + return (CGFloat)result; + } else if (dimpl) { + double result = dimpl(self, (double)t); + return (CGFloat)result; + } + return t; } @end @@ -62,5 +85,5 @@ CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPat } CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t) { - return [(CASpringAnimation *)animation _solveForInput:t]; + return [(CASpringAnimation *)animation valueAt:t]; } diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 669190bf5e..1fb1150a23 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -148,6 +148,7 @@ private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { let subtree = makeSubtreeSnapshot(layer: sublayer) if let subtree = subtree { subtree.frame = sublayer.frame + subtree.bounds = sublayer.bounds view.addSubview(subtree) } else { return nil @@ -157,6 +158,32 @@ private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { return view } +private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? { + let view = CALayer() + //view.layer.isHidden = layer.isHidden + view.opacity = layer.opacity + view.contents = layer.contents + view.contentsRect = layer.contentsRect + view.contentsScale = layer.contentsScale + view.contentsCenter = layer.contentsCenter + view.contentsGravity = layer.contentsGravity + view.masksToBounds = layer.masksToBounds + view.cornerRadius = layer.cornerRadius + if let sublayers = layer.sublayers { + for sublayer in sublayers { + let subtree = makeLayerSubtreeSnapshot(layer: sublayer) + if let subtree = subtree { + subtree.frame = sublayer.frame + subtree.bounds = sublayer.bounds + layer.addSublayer(subtree) + } else { + return nil + } + } + } + return view +} + public extension UIView { public func snapshotContentTree() -> UIView? { if let snapshot = makeSubtreeSnapshot(layer: self.layer) { @@ -168,6 +195,17 @@ public extension UIView { } } +public extension CALayer { + public func snapshotContentTree() -> CALayer? { + if let snapshot = makeLayerSubtreeSnapshot(layer: self) { + snapshot.frame = self.frame + return snapshot + } else { + return nil + } + } +} + public extension CGRect { public var topLeft: CGPoint { return self.origin diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 62e2c3d9b9..316fc4bca2 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -1,5 +1,10 @@ #import +typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) { + UIResponderDisableAutomaticKeyboardHandlingForward = 1 << 0, + UIResponderDisableAutomaticKeyboardHandlingBackward = 1 << 1 +}; + @interface UIViewController (Navigation) - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations; @@ -14,7 +19,7 @@ @interface UIView (Navigation) @property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer; -@property (nonatomic) bool disablesAutomaticKeyboardHandling; +@property (nonatomic) UIResponderDisableAutomaticKeyboardHandling disableAutomaticKeyboardHandling; - (void)input_setInputAccessoryHeightProvider:(CGFloat (^_Nullable)())block; - (CGFloat)input_getInputAccessoryHeight; diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index 77c3643a11..085c7be66d 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -35,7 +35,7 @@ static const void *UIViewControllerNavigationControllerKey = &UIViewControllerNa static const void *UIViewControllerPresentingControllerKey = &UIViewControllerPresentingControllerKey; static const void *UIViewControllerPresentingProxyControllerKey = &UIViewControllerPresentingProxyControllerKey; static const void *disablesInteractiveTransitionGestureRecognizerKey = &disablesInteractiveTransitionGestureRecognizerKey; -static const void *disablesAutomaticKeyboardHandlingKey = &disablesAutomaticKeyboardHandlingKey; +static const void *disableAutomaticKeyboardHandlingKey = &disableAutomaticKeyboardHandlingKey; static const void *setNeedsStatusBarAppearanceUpdateKey = &setNeedsStatusBarAppearanceUpdateKey; static const void *inputAccessoryHeightProviderKey = &inputAccessoryHeightProviderKey; @@ -218,12 +218,12 @@ static bool notyfyingShiftState = false; [self setAssociatedObject:@(disablesInteractiveTransitionGestureRecognizer) forKey:disablesInteractiveTransitionGestureRecognizerKey]; } -- (bool)disablesAutomaticKeyboardHandling { - return [[self associatedObjectForKey:disablesAutomaticKeyboardHandlingKey] boolValue]; +- (UIResponderDisableAutomaticKeyboardHandling)disableAutomaticKeyboardHandling { + return (UIResponderDisableAutomaticKeyboardHandling)[[self associatedObjectForKey:disableAutomaticKeyboardHandlingKey] unsignedIntegerValue]; } -- (void)setDisablesAutomaticKeyboardHandling:(bool)disablesAutomaticKeyboardHandling { - [self setAssociatedObject:@(disablesAutomaticKeyboardHandling) forKey:disablesAutomaticKeyboardHandlingKey]; +- (void)setDisableAutomaticKeyboardHandling:(UIResponderDisableAutomaticKeyboardHandling)disableAutomaticKeyboardHandling { + [self setAssociatedObject:@(disableAutomaticKeyboardHandling) forKey:disableAutomaticKeyboardHandlingKey]; } - (void)input_setInputAccessoryHeightProvider:(CGFloat (^_Nullable)())block { diff --git a/Display/ViewController.swift b/Display/ViewController.swift index f4f88ae472..c00c6f1ba1 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -46,8 +46,16 @@ open class ViewControllerPresentationArguments { } } - override open func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge { - return .bottom + public final var preferNavigationUIHidden: Bool = false { + didSet { + if self.preferNavigationUIHidden != oldValue { + self.window?.invalidatePreferNavigationUIHidden() + } + } + } + + override open func prefersHomeIndicatorAutoHidden() -> Bool { + return self.preferNavigationUIHidden } public private(set) var presentationArguments: Any? @@ -196,6 +204,7 @@ open class ViewControllerPresentationArguments { if let navigationBar = self.navigationBar { transition.updateFrame(node: navigationBar, frame: navigationBarFrame) + navigationBar.updateLayout(size: navigationBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) } self.presentationContext.containerLayoutUpdated(layout, transition: transition) diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 8525275dc0..d74be8d3a8 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -1,5 +1,6 @@ import Foundation import AsyncDisplayKit +import SwiftSignalKit private class WindowRootViewController: UIViewController { var presentController: ((UIViewController, Bool, (() -> Void)?) -> Void)? @@ -88,10 +89,10 @@ private struct UpdatingLayout { } } - mutating func update(size: CGSize, metrics: LayoutMetrics, forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { + mutating func update(size: CGSize, metrics: LayoutMetrics, safeInsets: UIEdgeInsets, forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) + self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) } @@ -146,7 +147,7 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container let resolvedStatusBarHeight: CGFloat? if let statusBarHeight = layout.statusBarHeight { if layout.forceInCallStatusBarText != nil { - resolvedStatusBarHeight = 40.0 + resolvedStatusBarHeight = max(40.0, layout.safeInsets.top) } else { resolvedStatusBarHeight = statusBarHeight } @@ -250,6 +251,7 @@ public final class WindowHostView { let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void let updateDeferScreenEdgeGestures: (UIRectEdge) -> Void + let updatePreferNavigationUIHidden: (Bool) -> Void var present: ((ViewController, PresentationSurfaceLevel) -> Void)? var presentNative: ((UIViewController) -> Void)? @@ -259,12 +261,15 @@ public final class WindowHostView { var isUpdatingOrientationLayout = false var hitTest: ((CGPoint, UIEvent?) -> UIView?)? var invalidateDeferScreenEdgeGesture: (() -> Void)? + var invalidatePreferNavigationUIHidden: (() -> Void)? + var cancelInteractiveKeyboardGestures: (() -> Void)? - init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void) { + init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePreferNavigationUIHidden: @escaping (Bool) -> Void) { self.view = view self.isRotating = isRotating self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations self.updateDeferScreenEdgeGestures = updateDeferScreenEdgeGestures + self.updatePreferNavigationUIHidden = updatePreferNavigationUIHidden } } @@ -276,12 +281,25 @@ public struct WindowTracingTags { public protocol WindowHost { func present(_ controller: ViewController, on level: PresentationSurfaceLevel) func invalidateDeferScreenEdgeGestures() + func invalidatePreferNavigationUIHidden() + func cancelInteractiveKeyboardGestures() } private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics { return LayoutMetrics(widthClass: .compact, heightClass: .compact) } +private func safeInsetsForScreenSize(_ size: CGSize) -> UIEdgeInsets { + if (size.width.isEqual(to: 375.0) && size.height.isEqual(to: 812.0)) || size.height.isEqual(to: 375.0) && size.width.isEqual(to: 812.0) { + if size.width.isEqual(to: 375.0) { + return UIEdgeInsets(top: 44.0, left: 0.0, bottom: 0.0, right: 0.0) + } else { + return UIEdgeInsets(top: 0.0, left: 44.0, bottom: 0.0, right: 44.0) + } + } + return UIEdgeInsets() +} + private final class KeyboardGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true @@ -300,6 +318,7 @@ public class Window1 { private let keyboardManager: KeyboardManager? private var statusBarChangeObserver: AnyObject? private var keyboardFrameChangeObserver: AnyObject? + private var keyboardTypeChangeObserver: AnyObject? private var windowLayout: WindowLayout private var updatingLayout: UpdatingLayout? @@ -312,6 +331,7 @@ public class Window1 { private var tracingStatusBarsInvalidated = false private var shouldUpdateDeferScreenEdgeGestures = false + private var shouldInvalidatePreferNavigationUIHidden = false private var statusBarHidden = false @@ -329,6 +349,8 @@ public class Window1 { private var keyboardGestureBeginLocation: CGPoint? private var keyboardGestureAccessoryHeight: CGFloat? + private var keyboardTypeChangeTimer: SwiftSignalKit.Timer? + public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) { self.hostView = hostView @@ -348,12 +370,10 @@ public class Window1 { var onScreenNavigationHeight: CGFloat? if (boundsSize.width.isEqual(to: 375.0) && boundsSize.height.isEqual(to: 812.0)) || boundsSize.height.isEqual(to: 375.0) && boundsSize.width.isEqual(to: 812.0) { - onScreenNavigationHeight = 20.0 + onScreenNavigationHeight = 34.0 } - let safeInsets = UIEdgeInsets() - - self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(self.hostView.view.bounds.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil) + self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(boundsSize), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsetsForScreenSize(boundsSize), onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil) self.presentationContext = PresentationContext() self.hostView.present = { [weak self] controller, level in @@ -388,6 +408,14 @@ public class Window1 { self?.invalidateDeferScreenEdgeGestures() } + self.hostView.invalidatePreferNavigationUIHidden = { [weak self] in + self?.invalidatePreferNavigationUIHidden() + } + + self.hostView.cancelInteractiveKeyboardGestures = { [weak self] in + self?.cancelInteractiveKeyboardGestures() + } + self.presentationContext.view = self.hostView.view self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) @@ -421,6 +449,34 @@ public class Window1 { } }) + if #available(iOSApplicationExtension 11.0, *) { + self.keyboardTypeChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextInputCurrentInputModeDidChange, object: nil, queue: nil, using: { [weak self] notification in + if let strongSelf = self, let initialInputHeight = strongSelf.windowLayout.inputHeight, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.view).0 { + if firstResponder.textInputMode?.primaryLanguage != nil { + return + } + + strongSelf.keyboardTypeChangeTimer?.invalidate() + let timer = SwiftSignalKit.Timer(timeout: 0.1, repeat: false, completion: { + if let strongSelf = self, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.view).0 { + if firstResponder.textInputMode?.primaryLanguage != nil { + return + } + + if let keyboardManager = strongSelf.keyboardManager { + let updatedKeyboardHeight = keyboardManager.getCurrentKeyboardHeight() + if !updatedKeyboardHeight.isEqual(to: initialInputHeight) { + strongSelf.updateLayout({ $0.update(inputHeight: updatedKeyboardHeight, transition: .immediate, overrideTransition: false) }) + } + } + } + }, queue: Queue.mainQueue()) + strongSelf.keyboardTypeChangeTimer = timer + timer.start() + } + }) + } + let recognizer = WindowPanRecognizer(target: self, action: #selector(self.panGesture(_:))) recognizer.cancelsTouchesInView = false recognizer.delaysTouchesBegan = false @@ -449,6 +505,9 @@ public class Window1 { if let keyboardFrameChangeObserver = self.keyboardFrameChangeObserver { NotificationCenter.default.removeObserver(keyboardFrameChangeObserver) } + if let keyboardTypeChangeObserver = self.keyboardTypeChangeObserver { + NotificationCenter.default.removeObserver(keyboardTypeChangeObserver) + } } public func setForceInCallStatusBar(_ forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)) { @@ -471,6 +530,23 @@ public class Window1 { self.hostView.view.setNeedsLayout() } + public func invalidatePreferNavigationUIHidden() { + self.shouldInvalidatePreferNavigationUIHidden = true + self.hostView.view.setNeedsLayout() + } + + public func cancelInteractiveKeyboardGestures() { + if self.windowLayout.upperKeyboardInputPositionBound != nil { + self.updateLayout { + $0.update(upperKeyboardInputPositionBound: nil, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false) + } + } + + if self.keyboardGestureBeginLocation != nil { + self.keyboardGestureBeginLocation = nil + } + } + public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for view in self.hostView.view.subviews.reversed() { if NSStringFromClass(type(of: view)) == "UITransitionView" { @@ -499,7 +575,7 @@ public class Window1 { } else { transition = .immediate } - self.updateLayout { $0.update(size: value, metrics: layoutMetricsForScreenSize(value), forceInCallStatusBarText: self.forceInCallStatusBarText, transition: transition, overrideTransition: true) } + self.updateLayout { $0.update(size: value, metrics: layoutMetricsForScreenSize(value), safeInsets: safeInsetsForScreenSize(value), forceInCallStatusBarText: self.forceInCallStatusBarText, transition: transition, overrideTransition: true) } } private var _rootController: ContainableController? @@ -563,7 +639,7 @@ public class Window1 { self.tracingStatusBarsInvalidated = false if self.statusBarHidden { - statusBarManager.updateState(surfaces: [], forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false) + statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false) } else { var statusBarSurfaces: [StatusBarSurface] = [] for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) { @@ -584,7 +660,7 @@ public class Window1 { } } self.cachedWindowSubviewCount = self.hostView.view.window?.subviews.count ?? 0 - statusBarManager.updateState(surfaces: statusBarSurfaces, forceInCallStatusBarText: self.forceInCallStatusBarText, forceHiddenBySystemWindows: hasPreview, animated: animatedUpdate) + statusBarManager.updateState(surfaces: statusBarSurfaces, withSafeInsets: !self.windowLayout.safeInsets.top.isZero, forceInCallStatusBarText: self.forceInCallStatusBarText, forceHiddenBySystemWindows: hasPreview, animated: animatedUpdate) } var keyboardSurfaces: [KeyboardSurface] = [] @@ -599,12 +675,16 @@ public class Window1 { self.hostView.updateSupportedInterfaceOrientations(self.presentationContext.combinedSupportedOrientations()) self.hostView.updateDeferScreenEdgeGestures(self.collectScreenEdgeGestures()) + self.hostView.updatePreferNavigationUIHidden(self.collectPreferNavigationUIHidden()) self.shouldUpdateDeferScreenEdgeGestures = false - } else if self.shouldUpdateDeferScreenEdgeGestures { + self.shouldInvalidatePreferNavigationUIHidden = false + } else if self.shouldUpdateDeferScreenEdgeGestures || self.shouldInvalidatePreferNavigationUIHidden { self.shouldUpdateDeferScreenEdgeGestures = false + self.shouldInvalidatePreferNavigationUIHidden = false self.hostView.updateDeferScreenEdgeGestures(self.collectScreenEdgeGestures()) + self.hostView.updatePreferNavigationUIHidden(self.collectPreferNavigationUIHidden()) } if !UIWindow.isDeviceRotating() { @@ -716,6 +796,10 @@ public class Window1 { } private func panGestureBegan(location: CGPoint) { + if self.windowLayout.upperKeyboardInputPositionBound != nil { + return + } + let keyboardGestureBeginLocation = location let view = self.hostView.view let (firstResponder, accessoryHeight) = getFirstResponderAndAccessoryHeight(view) @@ -746,9 +830,23 @@ public class Window1 { } private func panGestureEnded(location: CGPoint, velocity: CGPoint?) { + if self.keyboardGestureBeginLocation == nil { + return + } + self.keyboardGestureBeginLocation = nil let currentLocation = location - if let velocity = velocity, let inputHeight = self.windowLayout.inputHeight, velocity.y > 100.0 && currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0) > self.windowLayout.size.height - inputHeight { + + let accessoryHeight = (self.keyboardGestureAccessoryHeight ?? 0.0) + + var canDismiss = false + if let upperKeyboardInputPositionBound = self.windowLayout.upperKeyboardInputPositionBound, upperKeyboardInputPositionBound >= self.windowLayout.size.height - accessoryHeight { + canDismiss = true + } else if let velocity = velocity, velocity.y > 100.0 { + canDismiss = true + } + + if canDismiss, let inputHeight = self.windowLayout.inputHeight, currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0) > self.windowLayout.size.height - inputHeight { self.updateLayout { $0.update(upperKeyboardInputPositionBound: self.windowLayout.size.height, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false) } @@ -786,6 +884,10 @@ public class Window1 { return edges } + private func collectPreferNavigationUIHidden() -> Bool { + return false + } + public func forEachViewController(_ f: (ViewController) -> Bool) { for controller in self.presentationContext.controllers { if !f(controller) { diff --git a/DisplayMac/ASDisplayNode.swift b/DisplayMac/ASDisplayNode.swift new file mode 100644 index 0000000000..ed43abb43e --- /dev/null +++ b/DisplayMac/ASDisplayNode.swift @@ -0,0 +1,84 @@ +import Foundation + +open class ASDisplayNode: NSObject { + var layer: CALayer { + preconditionFailure() + } + + var view: UIView { + preconditionFailure() + } + + open var frame: CGRect { + get { + return self.layer.frame + } set(value) { + self.layer.frame = value + } + } + + open var bounds: CGRect { + get { + return self.layer.bounds + } set(value) { + self.layer.bounds = value + } + } + + open var position: CGPoint { + get { + return self.layer.position + } set(value) { + self.layer.position = value + } + } + + var alpha: CGFloat { + get { + return CGFloat(self.layer.opacity) + } set(value) { + self.layer.opacity = Float(value) + } + } + + var backgroundColor: UIColor? { + get { + if let backgroundColor = self.layer.backgroundColor { + return UIColor(cgColor: backgroundColor) + } else { + return nil + } + } set(value) { + self.layer.backgroundColor = value?.cgColor + } + } + + var isLayerBacked: Bool = false + + override init() { + super.init() + } + + func setLayerBlock(_ f: @escaping () -> CALayer) { + + } + + func setViewBlock(_ f: @escaping () -> UIView) { + + } + + open func layout() { + } + + open func addSubnode(_ subnode: ASDisplayNode) { + + } + + open func insertSubnode(belowSubnode: ASDisplayNode) { + + } + + open func insertSubnode(aboveSubnode: ASDisplayNode) { + + } +} diff --git a/DisplayMac/NSValueAdditions.swift b/DisplayMac/NSValueAdditions.swift new file mode 100644 index 0000000000..7f3047d0c8 --- /dev/null +++ b/DisplayMac/NSValueAdditions.swift @@ -0,0 +1,12 @@ +import Foundation +import Cocoa + +extension NSValue { + convenience init(cgRect: CGRect) { + self.init(rect: NSRect(origin: cgRect.origin, size: cgRect.size)) + } + + convenience init(cgPoint: CGPoint) { + self.init(point: NSPoint(x: cgPoint.x, y: cgPoint.y)) + } +} diff --git a/DisplayMac/UIGestureRecognizer.swift b/DisplayMac/UIGestureRecognizer.swift new file mode 100644 index 0000000000..b209e97153 --- /dev/null +++ b/DisplayMac/UIGestureRecognizer.swift @@ -0,0 +1,47 @@ +import Foundation + +public enum UIGestureRecognizerState : Int { + case possible + case began + case changed + case ended + case cancelled + case failed +} +open class UIGestureRecognizer: NSObject { + public init(target: Any?, action: Selector?) { + super.init() + } + + open var state: UIGestureRecognizerState = .possible { + didSet { + + } + } + + weak open var delegate: UIGestureRecognizerDelegate? + + open var isEnabled: Bool = true + + open var view: UIView? { + return nil + } + + open var cancelsTouchesInView: Bool = true + open var delaysTouchesBegan: Bool = false + open var delaysTouchesEnded: Bool = true + + open func location(in view: UIView?) -> CGPoint { + return CGPoint() + } + + open var numberOfTouches: Int { + return 0 + } +} + +@objc public protocol UIGestureRecognizerDelegate : NSObjectProtocol { + @objc optional func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool + @objc optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool + @objc optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool +} diff --git a/DisplayMac/UIKit.swift b/DisplayMac/UIKit.swift new file mode 100644 index 0000000000..54c06ae71c --- /dev/null +++ b/DisplayMac/UIKit.swift @@ -0,0 +1,71 @@ +import Foundation +import QuartzCore + +public struct UIEdgeInsets: Equatable { + public let top: CGFloat + public let left: CGFloat + public let bottom: CGFloat + public let right: CGFloat + + public init() { + self.top = 0.0 + self.left = 0.0 + self.bottom = 0.0 + self.right = 0.0 + } + + public init(top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat) { + self.top = top + self.left = left + self.bottom = bottom + self.right = right + } + + public static func ==(lhs: UIEdgeInsets, rhs: UIEdgeInsets) -> Bool { + if !lhs.top.isEqual(to: rhs.top) { + return false + } + if !lhs.left.isEqual(to: rhs.left) { + return false + } + if !lhs.bottom.isEqual(to: rhs.bottom) { + return false + } + if !lhs.right.isEqual(to: rhs.right) { + return false + } + return true + } +} + +public final class UIColor: NSObject { + let cgColor: CGColor + + init(rgb: Int32) { + preconditionFailure() + } + + init(cgColor: CGColor) { + self.cgColor = cgColor + } +} + +open class CASeeThroughTracingLayer: CALayer { + +} + +open class CASeeThroughTracingView: UIView { + +} + +func makeSpringAnimation(_ keyPath: String) -> CABasicAnimation { + return CABasicAnimation(keyPath: keyPath) +} + +func makeSpringBounceAnimation(_ keyPath: String, _ initialVelocity: CGFloat, _ damping: CGFloat) -> CABasicAnimation { + return CABasicAnimation(keyPath: keyPath) +} + +func springAnimationValueAt(_ animation: CABasicAnimation, _ t: CGFloat) -> CGFloat { + return t +} diff --git a/DisplayMac/UIScrollView.swift b/DisplayMac/UIScrollView.swift new file mode 100644 index 0000000000..d3c4276004 --- /dev/null +++ b/DisplayMac/UIScrollView.swift @@ -0,0 +1,24 @@ +import Foundation +import QuartzCore + +public protocol UIScrollViewDelegate { +} + +open class UIScrollView: UIView { + public var contentOffset: CGPoint { + get { + return self.bounds.origin + } set(value) { + self.bounds.origin = value + } + } + + public var contentSize: CGSize = CGSize() { + didSet { + + } + } + + public var alwaysBoundsVertical: Bool = false + public var alwaysBoundsHorizontal: Bool = false +} diff --git a/DisplayMac/UISlider.swift b/DisplayMac/UISlider.swift new file mode 100644 index 0000000000..622661f29b --- /dev/null +++ b/DisplayMac/UISlider.swift @@ -0,0 +1,5 @@ +import Foundation + +final class UISlider: UIView { + +} diff --git a/DisplayMac/UITouch.swift b/DisplayMac/UITouch.swift new file mode 100644 index 0000000000..21e0edace9 --- /dev/null +++ b/DisplayMac/UITouch.swift @@ -0,0 +1,5 @@ +import Foundation + +public final class UITouch: NSObject { + +} diff --git a/DisplayMac/UIView.swift b/DisplayMac/UIView.swift new file mode 100644 index 0000000000..5fa17be3da --- /dev/null +++ b/DisplayMac/UIView.swift @@ -0,0 +1,49 @@ +import Foundation +import QuartzCore + +open class UIView: NSObject { + public let layer: CALayer + + open var frame: CGRect { + get { + return self.layer.frame + } set(value) { + self.layer.frame = value + } + } + + open var bounds: CGRect { + get { + return self.layer.bounds + } set(value) { + self.layer.bounds = value + } + } + + open var center: CGPoint { + get { + return self.layer.position + } set(value) { + self.layer.position = value + } + } + + init(frame: CGRect) { + self.layer = CALayer() + self.layer.frame = frame + + super.init() + } + + convenience override init() { + self.init(frame: CGRect()) + } + + static func animationDurationFactor() -> Double { + return 1.0 + } + + public func bringSubview(toFront: UIView) { + + } +}