diff --git a/CXPopupKit.xcodeproj/project.pbxproj b/CXPopupKit.xcodeproj/project.pbxproj index 8404b9f..9566233 100644 --- a/CXPopupKit.xcodeproj/project.pbxproj +++ b/CXPopupKit.xcodeproj/project.pbxproj @@ -8,12 +8,8 @@ /* Begin PBXBuildFile section */ 3C1727671FE80F6400728374 /* CXPopupKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C1727651FE80F6400728374 /* CXPopupKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3C17276F1FE8145A00728374 /* SnapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C17276E1FE8145A00728374 /* SnapKit.framework */; }; 3C1727721FE814E500728374 /* CXPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C1727711FE814E500728374 /* CXPopup.swift */; }; 3C1727741FE81AB100728374 /* CXAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C1727731FE81AB100728374 /* CXAppearance.swift */; }; - 3C198D1620521D1300AA1C42 /* CXPopupable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C198D1520521D1300AA1C42 /* CXPopupable.swift */; }; - 3C198D1920521E1000AA1C42 /* CXPopupWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C198D1820521E1000AA1C42 /* CXPopupWindow.swift */; }; - 3C198D1C2052248000AA1C42 /* CXPopupLayoutStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C198D1B2052248000AA1C42 /* CXPopupLayoutStrategy.swift */; }; 3C1AB8471FEC0F710036C4F4 /* CXAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C1AB8461FEC0F710036C4F4 /* CXAnimation.swift */; }; 3C1AB8531FF1A34B0036C4F4 /* CXPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C1AB8521FF1A34B0036C4F4 /* CXPresentationController.swift */; }; 3C1AB8541FF1B6630036C4F4 /* SnapKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C17276E1FE8145A00728374 /* SnapKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -27,19 +23,12 @@ 3C2A21811FF8797600311FEE /* CXAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2A217E1FF8797600311FEE /* CXAlertView.swift */; }; 3C2A21851FF879FF00311FEE /* CXAlertAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2A21841FF879FF00311FEE /* CXAlertAppearance.swift */; }; 3C2F9C801FEB089E0055A2F0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2F9C7F1FEB089E0055A2F0 /* AppDelegate.swift */; }; + 3C2F9C821FEB089E0055A2F0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2F9C811FEB089E0055A2F0 /* ViewController.swift */; }; 3C2F9C851FEB089E0055A2F0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3C2F9C831FEB089E0055A2F0 /* Main.storyboard */; }; 3C2F9C871FEB089E0055A2F0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3C2F9C861FEB089E0055A2F0 /* Assets.xcassets */; }; 3C2F9C8A1FEB089E0055A2F0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3C2F9C881FEB089E0055A2F0 /* LaunchScreen.storyboard */; }; 3C2F9C8F1FEB43CC0055A2F0 /* CXPopupKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1727621FE80F6400728374 /* CXPopupKit.framework */; }; 3C2F9C901FEB43CC0055A2F0 /* CXPopupKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1727621FE80F6400728374 /* CXPopupKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 3CC3F4C92059B1BC001748C7 /* CXDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC3F4C82059B1BC001748C7 /* CXDatePicker.swift */; }; - 3CED126F2058118B001F9574 /* CXAlertViewLayoutStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CED126E2058118B001F9574 /* CXAlertViewLayoutStrategy.swift */; }; - 3CED127120581470001F9574 /* CXLifecycleAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CED127020581470001F9574 /* CXLifecycleAction.swift */; }; - 3CED127320584232001F9574 /* DemoListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CED127220584232001F9574 /* DemoListViewController.swift */; }; - 3CED12762058445C001F9574 /* BasicSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CED12752058445C001F9574 /* BasicSample.swift */; }; - 3CED127820584A38001F9574 /* MenuSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CED127720584A38001F9574 /* MenuSample.swift */; }; - 3CED127B20599314001F9574 /* CXPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CED127A20599314001F9574 /* CXPicker.swift */; }; - 3CED127D2059A3D5001F9574 /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CED127C2059A3D5001F9574 /* Person.swift */; }; 5A9F15A85663D1534C4FC8D0 /* LayoutUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9F162AD31CE7B55E967EE4 /* LayoutUtil.swift */; }; 5A9F19595301CBDD5B86AA12 /* PopupWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9F133B90F09142F0C4EF0C /* PopupWindow.swift */; }; /* End PBXBuildFile section */ @@ -76,9 +65,6 @@ 3C17276E1FE8145A00728374 /* SnapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SnapKit.framework; path = Carthage/Build/iOS/SnapKit.framework; sourceTree = ""; }; 3C1727711FE814E500728374 /* CXPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXPopup.swift; sourceTree = ""; }; 3C1727731FE81AB100728374 /* CXAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXAppearance.swift; sourceTree = ""; }; - 3C198D1520521D1300AA1C42 /* CXPopupable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXPopupable.swift; sourceTree = ""; }; - 3C198D1820521E1000AA1C42 /* CXPopupWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXPopupWindow.swift; sourceTree = ""; }; - 3C198D1B2052248000AA1C42 /* CXPopupLayoutStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXPopupLayoutStrategy.swift; sourceTree = ""; }; 3C1AB8461FEC0F710036C4F4 /* CXAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXAnimation.swift; sourceTree = ""; }; 3C1AB8521FF1A34B0036C4F4 /* CXPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXPresentationController.swift; sourceTree = ""; }; 3C1AB8551FF20C630036C4F4 /* CXAbstractPopupAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXAbstractPopupAnimation.swift; sourceTree = ""; }; @@ -92,18 +78,11 @@ 3C2A21841FF879FF00311FEE /* CXAlertAppearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CXAlertAppearance.swift; sourceTree = ""; }; 3C2F9C7D1FEB089E0055A2F0 /* CXPopupKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CXPopupKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3C2F9C7F1FEB089E0055A2F0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 3C2F9C811FEB089E0055A2F0 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 3C2F9C841FEB089E0055A2F0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 3C2F9C861FEB089E0055A2F0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 3C2F9C891FEB089E0055A2F0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 3C2F9C8B1FEB089E0055A2F0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3CC3F4C82059B1BC001748C7 /* CXDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXDatePicker.swift; sourceTree = ""; }; - 3CED126E2058118B001F9574 /* CXAlertViewLayoutStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXAlertViewLayoutStrategy.swift; sourceTree = ""; }; - 3CED127020581470001F9574 /* CXLifecycleAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXLifecycleAction.swift; sourceTree = ""; }; - 3CED127220584232001F9574 /* DemoListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoListViewController.swift; sourceTree = ""; }; - 3CED12752058445C001F9574 /* BasicSample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicSample.swift; sourceTree = ""; }; - 3CED127720584A38001F9574 /* MenuSample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuSample.swift; sourceTree = ""; }; - 3CED127A20599314001F9574 /* CXPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CXPicker.swift; sourceTree = ""; }; - 3CED127C2059A3D5001F9574 /* Person.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Person.swift; sourceTree = ""; }; 5A9F133B90F09142F0C4EF0C /* PopupWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopupWindow.swift; sourceTree = ""; }; 5A9F162AD31CE7B55E967EE4 /* LayoutUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutUtil.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -113,7 +92,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3C17276F1FE8145A00728374 /* SnapKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -150,7 +128,6 @@ 3C1727641FE80F6400728374 /* CXPopupKit */ = { isa = PBXGroup; children = ( - 3C198D1420521D0600AA1C42 /* Updated */, 3C2A21761FF7347300311FEE /* Components */, 3C1AB85F1FF334030036C4F4 /* Utils */, 3C1AB85E1FF333FD0036C4F4 /* Popup */, @@ -169,44 +146,6 @@ name = Frameworks; sourceTree = ""; }; - 3C198D1420521D0600AA1C42 /* Updated */ = { - isa = PBXGroup; - children = ( - 3C198D1720521DAD00AA1C42 /* Popup */, - ); - path = Updated; - sourceTree = ""; - }; - 3C198D1720521DAD00AA1C42 /* Popup */ = { - isa = PBXGroup; - children = ( - 3C1727711FE814E500728374 /* CXPopup.swift */, - 3C198D1520521D1300AA1C42 /* CXPopupable.swift */, - 3C1727731FE81AB100728374 /* CXAppearance.swift */, - 3C198D1820521E1000AA1C42 /* CXPopupWindow.swift */, - 3C1AB8521FF1A34B0036C4F4 /* CXPresentationController.swift */, - 3C198D1A2052247200AA1C42 /* Layout */, - 3CED127020581470001F9574 /* CXLifecycleAction.swift */, - ); - path = Popup; - sourceTree = ""; - }; - 3C198D1A2052247200AA1C42 /* Layout */ = { - isa = PBXGroup; - children = ( - 3C198D1B2052248000AA1C42 /* CXPopupLayoutStrategy.swift */, - ); - path = Layout; - sourceTree = ""; - }; - 3C198D1D205244DD00AA1C42 /* Layout */ = { - isa = PBXGroup; - children = ( - 3CED126E2058118B001F9574 /* CXAlertViewLayoutStrategy.swift */, - ); - path = Layout; - sourceTree = ""; - }; 3C1AB85D1FF333EE0036C4F4 /* Animation */ = { isa = PBXGroup; children = ( @@ -224,7 +163,10 @@ 3C1AB85E1FF333FD0036C4F4 /* Popup */ = { isa = PBXGroup; children = ( + 3C1727711FE814E500728374 /* CXPopup.swift */, + 3C1727731FE81AB100728374 /* CXAppearance.swift */, 5A9F133B90F09142F0C4EF0C /* PopupWindow.swift */, + 3C1AB8521FF1A34B0036C4F4 /* CXPresentationController.swift */, ); path = Popup; sourceTree = ""; @@ -241,7 +183,6 @@ 3C2A21761FF7347300311FEE /* Components */ = { isa = PBXGroup; children = ( - 3CED1279205992D7001F9574 /* Picker */, 3C2A217B1FF7384B00311FEE /* AlertView */, ); path = Components; @@ -250,7 +191,6 @@ 3C2A217B1FF7384B00311FEE /* AlertView */ = { isa = PBXGroup; children = ( - 3C198D1D205244DD00AA1C42 /* Layout */, 3C2A21841FF879FF00311FEE /* CXAlertAppearance.swift */, 3C2A217E1FF8797600311FEE /* CXAlertView.swift */, ); @@ -260,36 +200,16 @@ 3C2F9C7E1FEB089E0055A2F0 /* CXPopupKitDemo */ = { isa = PBXGroup; children = ( - 3CED127420584446001F9574 /* Samples */, 3C2F9C7F1FEB089E0055A2F0 /* AppDelegate.swift */, + 3C2F9C811FEB089E0055A2F0 /* ViewController.swift */, 3C2F9C831FEB089E0055A2F0 /* Main.storyboard */, 3C2F9C861FEB089E0055A2F0 /* Assets.xcassets */, 3C2F9C881FEB089E0055A2F0 /* LaunchScreen.storyboard */, 3C2F9C8B1FEB089E0055A2F0 /* Info.plist */, - 3CED127220584232001F9574 /* DemoListViewController.swift */, ); path = CXPopupKitDemo; sourceTree = ""; }; - 3CED127420584446001F9574 /* Samples */ = { - isa = PBXGroup; - children = ( - 3CED12752058445C001F9574 /* BasicSample.swift */, - 3CED127720584A38001F9574 /* MenuSample.swift */, - 3CED127C2059A3D5001F9574 /* Person.swift */, - ); - path = Samples; - sourceTree = ""; - }; - 3CED1279205992D7001F9574 /* Picker */ = { - isa = PBXGroup; - children = ( - 3CED127A20599314001F9574 /* CXPicker.swift */, - 3CC3F4C82059B1BC001748C7 /* CXDatePicker.swift */, - ); - path = Picker; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -312,6 +232,7 @@ 3C17275E1FE80F6400728374 /* Frameworks */, 3C17275F1FE80F6400728374 /* Headers */, 3C1727601FE80F6400728374 /* Resources */, + 3C1727701FE8147B00728374 /* ShellScript */, ); buildRules = ( ); @@ -401,6 +322,22 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 3C1727701FE8147B00728374 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/local/bin/carthage copy-frameworks"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 3C17275D1FE80F6400728374 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -408,8 +345,6 @@ files = ( 3C1AB8611FF3343B0036C4F4 /* CXFadePopupAnimation.swift in Sources */, 3C1AB8561FF20C630036C4F4 /* CXAbstractPopupAnimation.swift in Sources */, - 3C198D1C2052248000AA1C42 /* CXPopupLayoutStrategy.swift in Sources */, - 3CED126F2058118B001F9574 /* CXAlertViewLayoutStrategy.swift in Sources */, 3C1AB8581FF212420036C4F4 /* CXPlainPopupAnimation.swift in Sources */, 3C1727741FE81AB100728374 /* CXAppearance.swift in Sources */, 3C1727721FE814E500728374 /* CXPopup.swift in Sources */, @@ -418,16 +353,11 @@ 3C1AB8531FF1A34B0036C4F4 /* CXPresentationController.swift in Sources */, 3C2A21851FF879FF00311FEE /* CXAlertAppearance.swift in Sources */, 3C1AB8471FEC0F710036C4F4 /* CXAnimation.swift in Sources */, - 3CED127B20599314001F9574 /* CXPicker.swift in Sources */, 3C2A21711FF5F1FA00311FEE /* CXBouncePopupAnimation.swift in Sources */, - 3CC3F4C92059B1BC001748C7 /* CXDatePicker.swift in Sources */, - 3C198D1920521E1000AA1C42 /* CXPopupWindow.swift in Sources */, 5A9F15A85663D1534C4FC8D0 /* LayoutUtil.swift in Sources */, - 3CED127120581470001F9574 /* CXLifecycleAction.swift in Sources */, 5A9F19595301CBDD5B86AA12 /* PopupWindow.swift in Sources */, 3C2A21811FF8797600311FEE /* CXAlertView.swift in Sources */, 3C2A21751FF7325C00311FEE /* CXBounceZoomPopupAnimation.swift in Sources */, - 3C198D1620521D1300AA1C42 /* CXPopupable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -435,11 +365,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3CED127D2059A3D5001F9574 /* Person.swift in Sources */, + 3C2F9C821FEB089E0055A2F0 /* ViewController.swift in Sources */, 3C2F9C801FEB089E0055A2F0 /* AppDelegate.swift in Sources */, - 3CED127320584232001F9574 /* DemoListViewController.swift in Sources */, - 3CED127820584A38001F9574 /* MenuSample.swift in Sources */, - 3CED12762058445C001F9574 /* BasicSample.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/CXPopupKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/CXPopupKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/CXPopupKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/CXPopupKit.xcodeproj/xcshareddata/xcschemes/CXPopupKit.xcscheme b/CXPopupKit.xcodeproj/xcshareddata/xcschemes/CXPopupKit.xcscheme index 025d6a4..862bdaf 100644 --- a/CXPopupKit.xcodeproj/xcshareddata/xcschemes/CXPopupKit.xcscheme +++ b/CXPopupKit.xcodeproj/xcshareddata/xcschemes/CXPopupKit.xcscheme @@ -1,82 +1,82 @@ - - - + LastUpgradeVersion = "0920" + version = "1.3"> + + + + + + + + + + + + + + + + + BuildableIdentifier = "primary" + BlueprintIdentifier = "3C1727611FE80F6400728374" + BuildableName = "CXPopupKit.framework" + BlueprintName = "CXPopupKit" + ReferencedContainer = "container:CXPopupKit.xcodeproj"> - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/CXPopupKit/Components/AlertView/CXAlertAppearance.swift b/CXPopupKit/Components/AlertView/CXAlertAppearance.swift index b85249d..53c107b 100644 --- a/CXPopupKit/Components/AlertView/CXAlertAppearance.swift +++ b/CXPopupKit/Components/AlertView/CXAlertAppearance.swift @@ -21,6 +21,7 @@ public struct CXAlertAppearance { public struct Font { public var title = UIFont.boldSystemFont(ofSize: 16.0) public var detail = UIFont.systemFont(ofSize: 13.0) + public var cancel = UIFont.boldSystemFont(ofSize: 14.0) public var action = UIFont.boldSystemFont(ofSize: 14.0) } @@ -28,7 +29,9 @@ public struct CXAlertAppearance { public var backgroundColor: UIColor = .white public var title: UIColor = .black public var detail: UIColor = .black + public var cancelBackground: UIColor = .white public var actionBackground: UIColor = .white + public var cancelTitle: UIColor? = nil public var actionTitle: UIColor? = nil } @@ -40,7 +43,7 @@ public struct CXAlertAppearance { public struct Dimension { public var titleMargin: UIEdgeInsets = UIEdgeInsets(top: 24, left: 16, bottom: 0, right: 16) - public var messageMargin: UIEdgeInsets = UIEdgeInsets(top: 24, left: 8, bottom: 24, right: 8) + public var detailMargin: UIEdgeInsets = UIEdgeInsets(top: 24, left: 8, bottom: 24, right: 8) public var buttonHeight: CGFloat = 44 public var cornerRadius: CGFloat = 8.0 public var rectCorners: UIRectCorner = .allCorners @@ -53,29 +56,24 @@ public struct CXAlertAppearance { public var dimension = Dimension() public var color = Color() public var separator = Separator() - public var isModal = true { - didSet { - appearance.window.allowTouchOutsideToDismiss = !isModal - } - } + public var isModal = true + public init(with type: CXAlertType) { self.alertType = type switch alertType { - case .actionSheet: - appearance.window.width = .equalToParent - appearance.window.position = .bottom - appearance.window.enableInsideSafeArea = true - appearance.animation.style = .plain - appearance.animation.duration = CXAnimation.Duration(0.35) - appearance.animation.transition = CXAnimation.Transition(in: .up, out: .down) - case .alert: - appearance.window.width = .fixValue(size: 300) - appearance.animation.style = .bounceZoom - appearance.animation.duration = CXAnimation.Duration(0.35) - appearance.animation.transition = CXAnimation.Transition(.center) - appearance.window.allowTouchOutsideToDismiss = false + case .actionSheet: + appearance.window.width = .fixValue(size: UIScreen.main.bounds.width - 16) + appearance.window.position = .bottom + appearance.animation.style = .plain + appearance.animation.duration = CXAnimation.Duration(0.35) + appearance.animation.transition = CXAnimation.Transition(in: .up, out: .down) + case .alert: + appearance.window.width = .fixValue(size: 247) + appearance.animation.style = .bounceZoom + appearance.animation.duration = CXAnimation.Duration(0.35) + appearance.animation.transition = CXAnimation.Transition(.center) } } } diff --git a/CXPopupKit/Components/AlertView/CXAlertView.swift b/CXPopupKit/Components/AlertView/CXAlertView.swift index 6fa77c4..1841d6e 100644 --- a/CXPopupKit/Components/AlertView/CXAlertView.swift +++ b/CXPopupKit/Components/AlertView/CXAlertView.swift @@ -6,46 +6,80 @@ import UIKit import SnapKit -public final class CXAlertView: UIView, CXPopupable { +public final class CXAlertView: UIView { + public var alertAppearance: CXAlertAppearance - struct CXAlertContent { - let title: String? - let message: String? - let cancelButtonTitile: String? - let actionButtonTitles: [String] - } + let titleLabel = UILabel() + let detailLabel = UILabel() + let stackView = UIStackView() + var actionButtons = [UIButton]() - public let titleLabel = UILabel() - public let messageLabel = UILabel() - public var actionButtons = [UIButton]() - public var alertAppearance: CXAlertAppearance + var titleLabelHeight: CGFloat = 0 + var detailLabelHeight: CGFloat = 0 + var actionButtonsHeight: CGFloat = 0 + + var estimateViewHeight: CGFloat { + return titleLabelHeight + detailLabelHeight + actionButtonsHeight + } - private let stackView = UIStackView() - private let stackViewBackgroundView = UIView() - private let layoutStrategy: CXAlertViewLayoutStrategy - private let content: CXAlertContent private let cancelButtonTag = -1 + private let title: String? + private let detail: String? + private let cancel: String? + private let actions: [String] - public init(type: CXAlertType, title: String? = nil, message: String? = nil, cancel: String? = nil, actions: [String] = []) { - self.layoutStrategy = type == .alert ? CXAlertLayoutStrategy() : CXActionSheetStrategy() + public init(type: CXAlertType, title: String? = nil, detail: String? = nil, cancel: String? = nil, actions: [String] = []) { self.alertAppearance = type.appearance - self.content = CXAlertContent(title: title, message: message, cancelButtonTitile: cancel, actionButtonTitles: actions) + self.title = title + self.detail = detail + self.cancel = cancel + self.actions = actions super.init(frame: .zero) } - func setup() { - backgroundColor = alertAppearance.color.backgroundColor - layer.cornerRadius = alertAppearance.dimension.cornerRadius - layer.masksToBounds = true + public func show(at presenter: UIViewController?, positive: CXPopupHandler? = nil, negative: CXPopupHandler? = nil) { + setupContainer() + setupLayout() + + alertAppearance.appearance.window.allowTouchOutsideToDismiss = !alertAppearance.isModal + alertAppearance.appearance.window.height = .fixValue(size: estimateViewHeight) + self.cx.show(at: presenter, appearance: alertAppearance.appearance, positive: positive, negative: negative) + } + + func setupContainer() { + self.backgroundColor = alertAppearance.color.backgroundColor + self.layer.cornerRadius = alertAppearance.dimension.cornerRadius + self.layer.masksToBounds = true + + setupTitleLabel(with: title) + setupDetailLabel(with: detail) + setupActionButtons(with: actions, cancel: cancel) + } + + func setupStackView() { + let stackViewBackgroundView = UIView() + self.addSubview(stackViewBackgroundView) + LayoutUtil.fill(view: stackView, at: stackViewBackgroundView) + drawSeparator(at: stackViewBackgroundView) + if alertAppearance.separator.isEnabled { + stackViewBackgroundView.backgroundColor = alertAppearance.separator.color + stackView.spacing = alertAppearance.separator.width + } + stackViewBackgroundView.snp.makeConstraints { maker in + maker.leading.trailing.bottom.equalTo(self) + maker.top.equalTo(self.detailLabel.snp.bottom).offset(alertAppearance.dimension.detailMargin.bottom) + } + } + + func setupLayout() { + layoutActionButtons() + updateDetailLabelHeight() + updateTitleLabelHeight() - setupTitleLabel() - setupMessageLabel() - setupActionButton() - setupStackViewBackground() } - func setupTitleLabel() { - titleLabel.text = content.title + private func setupTitleLabel(with title: String?) { + titleLabel.text = title titleLabel.font = alertAppearance.font.title titleLabel.textAlignment = .center titleLabel.numberOfLines = 0 @@ -54,97 +88,138 @@ public final class CXAlertView: UIView, CXPopupable { addSubview(titleLabel) } - func setupMessageLabel() { - messageLabel.text = content.message - messageLabel.font = alertAppearance.font.detail - messageLabel.textAlignment = .center - messageLabel.numberOfLines = 0 - messageLabel.lineBreakMode = .byWordWrapping - addSubview(messageLabel) + private func updateTitleLabelHeight() { + let margin = alertAppearance.dimension.titleMargin + if case let .fixValue(size) = alertAppearance.appearance.window.width, let mText = titleLabel.text as NSString? { + let targetWidth = size - margin.left - margin.right + let targetHeight = LayoutUtil.getEstimateHeight(for: mText, with: targetWidth, and: alertAppearance.font.title) + + titleLabel.snp.makeConstraints { maker in + maker.leading.trailing.top.equalTo(self).inset(margin) + maker.height.equalTo(targetHeight) + } + titleLabelHeight = targetHeight + margin.top + margin.bottom + } else { + titleLabelHeight = 0 + titleLabel.snp.makeConstraints { maker in + maker.leading.trailing.top.equalTo(self) + maker.height.equalTo(titleLabelHeight) + } + } + } + + private func setupDetailLabel(with detail: String?) { + detailLabel.text = detail + detailLabel.font = alertAppearance.font.detail + detailLabel.textAlignment = .center + detailLabel.numberOfLines = 0 + detailLabel.lineBreakMode = .byWordWrapping + addSubview(detailLabel) } - func setupActionButton() { - for actionTitle in content.actionButtonTitles { - actionButtons.append(getActionButton(with: actionTitle)) + private func updateDetailLabelHeight() { + let margin = alertAppearance.dimension.detailMargin + if case let .fixValue(size) = alertAppearance.appearance.window.width, let mText = detailLabel.text as NSString? { + let targetWidth = size - margin.left - margin.right + let targetHeight = LayoutUtil.getEstimateHeight(for: mText, with: targetWidth, and: alertAppearance.font.detail) + + detailLabel.snp.makeConstraints { maker in + maker.leading.trailing.equalTo(self).inset(margin) + maker.top.equalTo(titleLabel.snp.bottom).offset(margin.top) + maker.height.equalTo(targetHeight) + } + detailLabelHeight = targetHeight + margin.top + margin.bottom + } else { + detailLabelHeight = margin.top + margin.bottom + detailLabel.snp.makeConstraints { maker in + maker.leading.trailing.equalTo(self).inset(margin) + maker.top.equalTo(titleLabel.snp.bottom).offset(margin.top) + maker.height.equalTo(detailLabelHeight) + } } + } - if let cancelButtonTitle = content.cancelButtonTitile { - let cancelButton = getActionButton(with: cancelButtonTitle) - cancelButton.tag = cancelButtonTag - actionButtons.append(cancelButton) + private func setupActionButtons(with actions: [String], cancel: String?) { + for (index, action) in actions.enumerated() { + createActionButton(action: action, tag: index) } - actionButtons.forEach { - stackView.addArrangedSubview($0) + if let mCancel = cancel { + createActionButton(action: mCancel, tag: cancelButtonTag) } } - func getActionButton(with buttonTitle: String) -> UIButton { + private func createActionButton(action: String, tag: Int) { let button = UIButton(type: .system) - button.setTitle(buttonTitle, for: .normal) - button.titleLabel?.font = alertAppearance.font.action - button.backgroundColor = alertAppearance.color.actionBackground - button.setTitleColor(alertAppearance.color.actionTitle, for: .normal) - button.addTarget(self, action: #selector(actionTriggered(button:)), for: .touchUpInside) - return button + button.tag = tag + setup(button: button, with: action, and: tag) } - func setupStackViewBackground() { - self.addSubview(stackViewBackgroundView) - stackView.alignment = .fill - stackView.distribution = .fillEqually - stackView.axis = alertAppearance.alertType == .actionSheet ? .vertical : actionButtons.count < 3 ? .horizontal : .vertical + private func setup(button: UIButton, with action: String, and tag: Int) { + let buttonFont = tag == cancelButtonTag ? alertAppearance.font.cancel : alertAppearance.font.action + let buttonTitleColor = tag == cancelButtonTag ? alertAppearance.color.cancelTitle : alertAppearance.color.actionTitle + let buttonBackgroundColor = tag == cancelButtonTag ? alertAppearance.color.cancelBackground : alertAppearance.color.actionBackground - if alertAppearance.separator.isEnabled { - stackViewBackgroundView.backgroundColor = alertAppearance.separator.color - stackView.spacing = alertAppearance.separator.width - } + button.setTitle(action, for: .normal) + button.titleLabel?.font = buttonFont + button.backgroundColor = buttonBackgroundColor + button.setTitleColor(buttonTitleColor, for: .normal) - stackViewBackgroundView.addSubview(stackView) - stackView.snp.makeConstraints { (maker) in - maker.leading.trailing.bottom.equalTo(stackViewBackgroundView) - maker.top.equalTo(stackViewBackgroundView).offset(1) - } - - stackViewBackgroundView.snp.makeConstraints { maker in - maker.leading.trailing.bottom.equalTo(self) - maker.top.equalTo(self.messageLabel.snp.bottom).offset(alertAppearance.dimension.messageMargin.bottom) - } - } - - func setupLayout() { - var height: CGFloat = 0 - height += layoutStrategy.layout(titleLabel: titleLabel, at: self, alertAppearance: alertAppearance) - height += layoutStrategy.layout(messageLabel: messageLabel, based: titleLabel, at: self, alertAppearance: alertAppearance) - height += layoutStrategy.layout(isVertical: stackView.axis == .vertical, actionCount: actionButtons.count, stackViewBackgroundView: stackViewBackgroundView, based: messageLabel, at: self, alertAppearance: alertAppearance) - alertAppearance.appearance.window.height = .fixValue(size: height) + actionButtons.append(button) + button.addTarget(self, action: #selector(actionTriggered(button:)), for: .touchUpInside) } @objc func actionTriggered(button: UIButton) { - let result = button.title(for: .normal) if button.tag == cancelButtonTag { - self.popup?.completeWithNegative(result: result) + self.cx.completeWithNegativeAction(result: button.title(for: .normal)) } else { - self.popup?.completeWithPositive(result: result) + self.cx.completeWithPositiveAction(result: button.title(for: .normal)) } } - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") + private func layoutActionButtons() { + setupStackView() + stackView.alignment = .fill + stackView.distribution = .fillEqually + if alertAppearance.alertType == .alert && actionButtons.count == 2 { + if let left = actionButtons.first, let right = actionButtons.last { + stackView.axis = .horizontal + stackView.addArrangedSubview(left) + stackView.addArrangedSubview(right) + } + } else { + for button in actionButtons { + stackView.axis = .vertical + stackView.addArrangedSubview(button) + } + } + updateActionButtonsHeight() } - public func createPopup() -> CXPopup { - setup() - setupLayout() - if alertAppearance.alertType == .actionSheet { - alertAppearance.appearance.window.backgroundColor = self.backgroundColor ?? .white + private func updateActionButtonsHeight() { + if alertAppearance.alertType == .alert && actionButtons.count == 2 { + actionButtonsHeight = alertAppearance.dimension.buttonHeight } else { - alertAppearance.appearance.window.backgroundColor = .clear + actionButtonsHeight = alertAppearance.dimension.buttonHeight * CGFloat(actionButtons.count) + } + stackView.snp.makeConstraints { maker in + maker.height.equalTo(actionButtonsHeight) } - return CXPopup(with: self, appearance: alertAppearance.appearance) } - public func show(at presenter: UIViewController?) { - createPopup().show(at: presenter) + private func drawSeparator(at parent: UIView) { + if alertAppearance.separator.isEnabled { + let separator = UIView(frame: .zero) + parent.addSubview(separator) + separator.backgroundColor = alertAppearance.separator.color + separator.snp.makeConstraints { maker in + maker.leading.trailing.top.equalTo(self.stackView) + maker.height.equalTo(alertAppearance.separator.width) + } + } + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") } } diff --git a/CXPopupKit/Components/AlertView/Layout/CXAlertViewLayoutStrategy.swift b/CXPopupKit/Components/AlertView/Layout/CXAlertViewLayoutStrategy.swift deleted file mode 100644 index ed22313..0000000 --- a/CXPopupKit/Components/AlertView/Layout/CXAlertViewLayoutStrategy.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// CXAlertViewLayoutStrategy.swift -// CXPopupKit -// -// Created by Cunqi Xiao on 3/13/18. -// Copyright © 2018 Cunqi Xiao. All rights reserved. -// - -import UIKit -import SnapKit - -protocol CXAlertViewLayoutStrategy { - func layout(titleLabel: UILabel, at parent: UIView, alertAppearance: CXAlertAppearance) -> CGFloat - func layout(messageLabel: UILabel, based titleLabel: UILabel, at parent: UIView, alertAppearance: CXAlertAppearance) -> CGFloat - func layout(isVertical: Bool, actionCount: Int, stackViewBackgroundView: UIView, based detailLabel: UILabel, at parent: UIView, alertAppearance: CXAlertAppearance) -> CGFloat -} - -extension CXAlertViewLayoutStrategy { - func layout(titleLabel: UILabel, at parent: UIView, alertAppearance: CXAlertAppearance) -> CGFloat { - if let mContent = titleLabel.text as NSString? { - let margin = alertAppearance.dimension.titleMargin - let width = alertAppearance.appearance.window.width.adjustedValue - margin.left - margin.right - let titleLabelHeight = LayoutUtil.getEstimateHeight(for: mContent, with: width, and: titleLabel.font) - titleLabel.snp.makeConstraints({ (maker) in - maker.leading.trailing.top.equalTo(parent).inset(margin) - maker.height.equalTo(titleLabelHeight) - }) - return titleLabelHeight + margin.top - } else { - titleLabel.snp.makeConstraints({ (maker) in - maker.leading.trailing.top.equalTo(parent) - maker.height.equalTo(0) - }) - return 0 - } - } - - func layout(messageLabel: UILabel, based titleLabel: UILabel, at parent: UIView, alertAppearance: CXAlertAppearance) -> CGFloat { - let titleMargin = alertAppearance.dimension.titleMargin - let margin = alertAppearance.dimension.messageMargin - let width = alertAppearance.appearance.window.width.adjustedValue - margin.left - margin.right - if let mContent = messageLabel.text as NSString? { - let height = LayoutUtil.getEstimateHeight(for: mContent, with: width, and: messageLabel.font) - messageLabel.snp.makeConstraints({ (maker) in - maker.leading.trailing.equalTo(parent).inset(margin) - maker.top.equalTo(titleLabel.snp.bottom).offset(margin.top + titleMargin.bottom) - maker.height.equalTo(height) - }) - return height + margin.top + titleMargin.bottom - } else { - messageLabel.snp.makeConstraints({ (maker) in - maker.leading.trailing.equalTo(parent) - maker.top.equalTo(titleLabel.snp.bottom) - maker.height.equalTo(0) - }) - return 0 - } - } -} - -class CXAlertLayoutStrategy: CXAlertViewLayoutStrategy { - func layout(isVertical: Bool, actionCount: Int, stackViewBackgroundView: UIView, based detailLabel: UILabel, at parent: UIView, alertAppearance: CXAlertAppearance) -> CGFloat { - let messageLabelMargin = alertAppearance.dimension.messageMargin - let height: CGFloat = isVertical ? CGFloat(actionCount) * alertAppearance.dimension.buttonHeight : alertAppearance.dimension.buttonHeight - - stackViewBackgroundView.snp.makeConstraints { (maker) in - maker.bottom.equalTo(parent) - } - return height + messageLabelMargin.bottom - } -} - -class CXActionSheetStrategy: CXAlertViewLayoutStrategy { - func layout(isVertical: Bool, actionCount: Int, stackViewBackgroundView: UIView, based detailLabel: UILabel, at parent: UIView, alertAppearance: CXAlertAppearance) -> CGFloat { - let messageLabelMargin = alertAppearance.dimension.messageMargin - let height: CGFloat = isVertical ? CGFloat(actionCount) * alertAppearance.dimension.buttonHeight : alertAppearance.dimension.buttonHeight - - stackViewBackgroundView.snp.makeConstraints { (maker) in - maker.bottom.equalTo(parent) - } - return height + messageLabelMargin.bottom - } -} diff --git a/CXPopupKit/Components/Picker/CXDatePicker.swift b/CXPopupKit/Components/Picker/CXDatePicker.swift deleted file mode 100644 index b8c596f..0000000 --- a/CXPopupKit/Components/Picker/CXDatePicker.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// CXDatePicker.swift -// CXPopupKit -// -// Created by Cunqi Xiao on 3/14/18. -// Copyright © 2018 Cunqi Xiao. All rights reserved. -// - -import UIKit -import SnapKit - -public class CXDatePicker: UIView, CXPopupable { - public let picker = UIDatePicker() - public let navigationBar = UINavigationBar() - public var title: String? - public var cancelButtonText: String? - public var doneButtonText: String? - - var appearance: CXAppearance = { - var appearance = CXAppearance() - appearance.window.width = .equalToParent - appearance.window.height = .fixValue(size: 320) - appearance.window.allowTouchOutsideToDismiss = false - appearance.window.enableInsideSafeArea = true - appearance.window.position = .bottom - appearance.animation.duration = CXAnimation.Duration(0.35) - appearance.animation.transition = CXAnimation.Transition(in: .up, out: .down) - appearance.animation.style = .plain - return appearance - }() - private var selectedDate: Date = Date() - - public func createPopup() -> CXPopup { - setup() - appearance.window.backgroundColor = picker.backgroundColor ?? .white - return CXPopup(with: self, appearance: self.appearance) - } - - public init(startDate: Date?, mode: UIDatePickerMode, title: String?, cancelButtonText: String?, doneButtonText: String?) { - super.init(frame: .zero) - self.selectedDate = startDate ?? Date() - self.title = title - self.cancelButtonText = cancelButtonText - self.doneButtonText = doneButtonText - self.picker.datePickerMode = mode - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setup() { - picker.setDate(selectedDate, animated: true) - setupNavigationBar() - setupLayout() - } - - private func setupLayout() { - self.addSubview(navigationBar) - navigationBar.snp.makeConstraints { (maker) in - maker.leading.top.trailing.equalTo(self) - maker.height.equalTo(44) - } - - self.addSubview(picker) - picker.snp.makeConstraints { (maker) in - maker.leading.trailing.bottom.equalTo(self) - maker.top.equalTo(navigationBar.snp.bottom) - } - } - - private func setupNavigationBar() { - let navigationItem = UINavigationItem(title: title ?? "") - navigationItem.leftBarButtonItem = UIBarButtonItem(title: cancelButtonText, style: .plain, target: self, action: #selector(didTapCancelButton)) - navigationItem.rightBarButtonItem = UIBarButtonItem(title: doneButtonText, style: .done, target: self, action: #selector(didTapDoneButton)) - navigationBar.pushItem(navigationItem, animated: true) - } - - @objc private func didTapCancelButton(_ sender: Any) { - popup?.completeWithNegative(result: nil) - } - - @objc private func didTapDoneButton(_ sender: Any) { - popup?.completeWithPositive(result: picker.date) - } -} diff --git a/CXPopupKit/Components/Picker/CXPicker.swift b/CXPopupKit/Components/Picker/CXPicker.swift deleted file mode 100644 index 1c4abb1..0000000 --- a/CXPopupKit/Components/Picker/CXPicker.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// CXPicker.swift -// CXPopupKit -// -// Created by Cunqi Xiao on 3/14/18. -// Copyright © 2018 Cunqi Xiao. All rights reserved. -// - -import UIKit -import SnapKit - -public protocol CXPickerExpressible { - func getCXPickerOptionName() -> String -} - -extension String: CXPickerExpressible { - public func getCXPickerOptionName() -> String { - return self - } -} - -public class CXPicker: UIView, CXPopupable { - public let picker = UIPickerView() - public let navigationBar = UINavigationBar() - public var options = [[CXPickerExpressible]]() - public var rowHeight: CGFloat = 44 - public var title: String? - public var cancelButtonText: String? - public var doneButtonText: String? - - var appearance: CXAppearance = { - var appearance = CXAppearance() - appearance.window.width = .equalToParent - appearance.window.height = .fixValue(size: 320) - appearance.window.allowTouchOutsideToDismiss = false - appearance.window.enableInsideSafeArea = true - appearance.window.position = .bottom - appearance.animation.duration = CXAnimation.Duration(0.35) - appearance.animation.transition = CXAnimation.Transition(in: .up, out: .down) - appearance.animation.style = .plain - return appearance - }() - - private var selectedOptions = [CXPickerExpressible]() - - public func createPopup() -> CXPopup { - setup() - appearance.window.backgroundColor = picker.backgroundColor! - return CXPopup(with: self, appearance: self.appearance) - } - - public init(with singleColumOptions: [CXPickerExpressible], title: String?, cancelButtonText: String?, doneButtonText: String?) { - super.init(frame: .zero) - self.options = [singleColumOptions] - self.title = title - self.cancelButtonText = cancelButtonText - self.doneButtonText = doneButtonText - } - - public init(with options: [[CXPickerExpressible]], title: String?, cancelButtonText: String?, doneButtonText: String?) { - super.init(frame: .zero) - self.options = options - self.title = title - self.cancelButtonText = cancelButtonText - self.doneButtonText = doneButtonText - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setup() { - setupPicker() - setupNavigationBar() - setupLayout() - } - - private func setupLayout() { - self.addSubview(navigationBar) - navigationBar.snp.makeConstraints { (maker) in - maker.leading.top.trailing.equalTo(self) - maker.height.equalTo(44) - } - - self.addSubview(picker) - picker.snp.makeConstraints { (maker) in - maker.leading.trailing.bottom.equalTo(self) - maker.top.equalTo(navigationBar.snp.bottom) - } - } - - private func setupPicker() { - self.picker.dataSource = self - self.picker.delegate = self - self.picker.backgroundColor = .white - } - - private func setupNavigationBar() { - let navigationItem = UINavigationItem(title: title ?? "") - navigationItem.leftBarButtonItem = UIBarButtonItem(title: cancelButtonText, style: .plain, target: self, action: #selector(didTapCancelButton)) - navigationItem.rightBarButtonItem = UIBarButtonItem(title: doneButtonText, style: .done, target: self, action: #selector(didTapDoneButton)) - navigationBar.pushItem(navigationItem, animated: true) - } - - @objc private func didTapCancelButton(_ sender: Any) { - popup?.completeWithNegative(result: nil) - } - - @objc private func didTapDoneButton(_ sender: Any) { - for component in 0 ..< options.count { - let row = picker.selectedRow(inComponent: component) - selectedOptions.append(options[component][row]) - } - - if selectedOptions.count < 2 { - popup?.completeWithPositive(result: selectedOptions.first) - } else { - popup?.completeWithPositive(result: selectedOptions) - } - } -} - -extension CXPicker: UIPickerViewDataSource { - public func numberOfComponents(in pickerView: UIPickerView) -> Int { - return options.count - } - - public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - return options[component].count - } -} - -extension CXPicker: UIPickerViewDelegate { - public func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - return rowHeight - } - - public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - let option = options[component][row] - return option.getCXPickerOptionName() - } -} diff --git a/CXPopupKit/Updated/Popup/CXAppearance.swift b/CXPopupKit/Popup/CXAppearance.swift similarity index 80% rename from CXPopupKit/Updated/Popup/CXAppearance.swift rename to CXPopupKit/Popup/CXAppearance.swift index 42c2413..a4b8975 100644 --- a/CXPopupKit/Updated/Popup/CXAppearance.swift +++ b/CXPopupKit/Popup/CXAppearance.swift @@ -30,21 +30,9 @@ public struct CXAppearance { public var allowTouchOutsideToDismiss = true public var maskBackgroundColor: UIColor = .black public var maskBackgroundAlpha: CGFloat = 0.3 - public var margin: UIEdgeInsets = .zero - public var isSafeAreaEnabled: Bool = true { - didSet { - if enableInsideSafeArea { - isSafeAreaEnabled = false - } - } - } - public var enableInsideSafeArea: Bool = false { - didSet { - if enableInsideSafeArea { - isSafeAreaEnabled = false - } - } - } + + public var isSafeAreaEnabled: Bool = true + public var shouldFillOutSafeArea: Bool = false } public struct Orientation { @@ -70,7 +58,7 @@ public struct CXAppearance { var adjustedValue: CGFloat { switch self { case .equalToParent: - return 1 + return 0 case let .partOfParent(percent): return percent <= 0 ? 0 : percent >= 1 ? 1 : percent case let .fixValue(size): diff --git a/CXPopupKit/Popup/CXPopup.swift b/CXPopupKit/Popup/CXPopup.swift new file mode 100644 index 0000000..d71fc78 --- /dev/null +++ b/CXPopupKit/Popup/CXPopup.swift @@ -0,0 +1,95 @@ +// +// CXPopup.swift +// CXPopupKit +// +// Created by Cunqi Xiao on 12/18/17. +// Copyright © 2017 Cunqi Xiao. All rights reserved. +// + +import UIKit +import SnapKit + +public typealias CXPopupHandler = (Any?) -> Void +public typealias CXPopupLifeCycleAction = () -> Void + +public final class Popupable { + public let base: Base + + init(base: Base) { + self.base = base + } + + deinit { + print("RELEASED") + } + + var window: PopupWindow? { + guard let mContent = base as? UIView else { + return nil + } + + var responder: UIResponder? = mContent + while responder != nil { + responder = responder?.next + + if let window = responder as? PopupWindow { + return window + } + } + return nil + } + + public func completeWithPositiveAction(result: Any?) { + DispatchQueue.main.async { + self.window?.positiveAction?(result) + self.window?.dismiss(animated: true) + } + } + + public func completeWithNegativeAction(result: Any?) { + DispatchQueue.main.async { + self.window?.negativeAction?(result) + self.window?.dismiss(animated: true) + } + } + + public func dismiss() { + DispatchQueue.main.async { + self.window?.dismiss(animated: true) + } + } + + @discardableResult public func show(at presenter: UIViewController?, appearance: CXAppearance = CXAppearance(), positive: CXPopupHandler? = nil, negative: CXPopupHandler? = nil, viewDidLoad: CXPopupLifeCycleAction? = nil, viewDidDisappear: CXPopupLifeCycleAction? = nil) -> PopupWindow { + guard let mContent = base as? UIView else { + return PopupWindow() + } + + let popupWindow = PopupWindow() + let presentationController = CXPresentationController(presentedViewController: popupWindow, presenting: presenter) + popupWindow.transitioningDelegate = presentationController + popupWindow.content = mContent + popupWindow.positiveAction = positive + popupWindow.negativeAction = negative + popupWindow.viewDidLoadAction = viewDidLoad + popupWindow.viewDidDisappearAction = viewDidDisappear + + popupWindow.appearance = appearance + presentationController.appearance = appearance + + presenter?.present(popupWindow, animated: true, completion: nil) + return popupWindow + } +} + +public protocol PopupableCompatible { + associatedtype CompatibleType + var cx: Popupable { get } +} + +public extension PopupableCompatible { + public var cx: Popupable { + return Popupable(base: self) + } +} + +extension UIView: PopupableCompatible {} diff --git a/CXPopupKit/Popup/CXPresentationController.swift b/CXPopupKit/Popup/CXPresentationController.swift new file mode 100644 index 0000000..c5d9e23 --- /dev/null +++ b/CXPopupKit/Popup/CXPresentationController.swift @@ -0,0 +1,132 @@ +// +// CXPresentationController.swift +// CXPopupKit +// +// Created by Cunqi Xiao on 12/25/17. +// Copyright © 2017 Cunqi Xiao. All rights reserved. +// + +import UIKit + +final class CXPresentationController: UIPresentationController, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning { + var appearance: CXAppearance! + var presentationWrapperView: UIView! + var backgroundView: UIView! + + override var frameOfPresentedViewInContainerView: CGRect { + let frame = LayoutUtil.presentedViewSize(in: containerView!, + width: appearance.window.width, + height: appearance.window.height, + attached: appearance.window.position, + isFollowingSafeAreaGuide: appearance.window.isSafeAreaEnabled, + shouldFillOutSafeArea: appearance.window.shouldFillOutSafeArea) + return frame + } + + public override var presentedView: UIView? { + return presentationWrapperView + } + + override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) { + super.init(presentedViewController: presentedViewController, presenting: presentingViewController) + presentedViewController.modalPresentationStyle = .custom + } + + public override func presentationTransitionWillBegin() { + self.presentationWrapperView = setupPresentationWrapperView() + + let presentedViewControllerView = super.presentedView! + presentedViewControllerView.frame = presentationWrapperView!.bounds + presentedViewControllerView.autoresizingMask = [.flexibleHeight, .flexibleWidth] + presentationWrapperView.addSubview(presentedViewControllerView) + + backgroundView = setupBackgroundView() + let coordinator = self.presentingViewController.transitionCoordinator + backgroundView.alpha = 0 + coordinator?.animate(alongsideTransition: { [unowned self] context in + self.backgroundView.alpha = self.appearance.window.maskBackgroundAlpha + }, completion: nil) + } + + private func setupPresentationWrapperView() -> UIView { + let presentationWrapperView = UIView(frame: self.frameOfPresentedViewInContainerView) + if appearance.shadow.isEnabled { + presentationWrapperView.layer.shadowColor = appearance.shadow.color.cgColor + presentationWrapperView.layer.shadowOffset = appearance.shadow.offset + presentationWrapperView.layer.shadowRadius = appearance.shadow.radius + presentationWrapperView.layer.shadowOpacity = appearance.shadow.opacity + } + return presentationWrapperView + } + + private func setupBackgroundView() -> UIView { + let backgroundView = UIView(frame: self.containerView!.bounds) + backgroundView.backgroundColor = appearance.window.maskBackgroundColor + backgroundView.alpha = appearance.window.maskBackgroundAlpha + backgroundView.isOpaque = false + + if appearance.window.allowTouchOutsideToDismiss { + backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(backgroundViewTapped))) + } + + self.containerView?.addSubview(backgroundView) + return backgroundView + } + + @objc private func backgroundViewTapped(gesture: UITapGestureRecognizer) { + self.presentingViewController.dismiss(animated: true, completion: nil) + } + + public override func presentationTransitionDidEnd(_ completed: Bool) { + if !completed { + self.presentationWrapperView = nil + self.backgroundView = nil + } + } + + public override func dismissalTransitionWillBegin() { + let coordinator = self.presentingViewController.transitionCoordinator + coordinator?.animate(alongsideTransition: { [weak self] context in + self?.backgroundView.alpha = 0 + }, completion: nil) + } + + public override func dismissalTransitionDidEnd(_ completed: Bool) { + if !completed { + self.presentationWrapperView = nil + self.backgroundView = nil + } + } + + public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + guard let fromViewController = transitionContext?.viewController(forKey: .from) else { + return appearance.animation.duration.out + } + let isPresenting = fromViewController == self.presentingViewController + return isPresenting ? appearance.animation.duration.in : appearance.animation.duration.out + } + + public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + let isPresenting = transitionContext.viewController(forKey: .from) == self.presentingViewController + let animation = appearance.animation.getAnimation(transitionContext, isPresenting) + animation.execute() + } + + public override func containerViewWillLayoutSubviews() { + super.containerViewWillLayoutSubviews() + self.backgroundView.frame = self.containerView!.bounds + self.presentationWrapperView.frame = self.frameOfPresentedViewInContainerView + } + + public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { + return self + } + + public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return self + } + + public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return self + } +} diff --git a/CXPopupKit/Popup/PopupWindow.swift b/CXPopupKit/Popup/PopupWindow.swift index a767bfa..ceb2d31 100644 --- a/CXPopupKit/Popup/PopupWindow.swift +++ b/CXPopupKit/Popup/PopupWindow.swift @@ -43,20 +43,16 @@ public final class PopupWindow: UIViewController { } private func setup() { - let window = appearance.window - view.backgroundColor = window.backgroundColor - - let strategy = getLayoutStrategy() - strategy.install(content ?? UIView(), into: view, basedOn: appearance) - } - - private func getLayoutStrategy() -> CXPopupLayoutStrategy { - let w = appearance.window - - if !w.isSafeAreaEnabled { - return LayoutWithoutSafeAreaStrategy() + let shouldFollowingSafeAreaGuide = appearance.window.isSafeAreaEnabled ? appearance.window.shouldFillOutSafeArea : false + view.backgroundColor = appearance.window.backgroundColor + if appearance.window.shouldFillOutSafeArea { + LayoutUtil.install(view: content, + at: view, + attached: appearance.window.position, + insets: LayoutUtil.convertWithSafeArea(isFollowingSafeAreaGuide: shouldFollowingSafeAreaGuide, at: UIApplication.shared.keyWindow), + isFollowingSafeAreaGuide: false) } else { - return LayoutWithOutsideSafeAreaStrategy() + LayoutUtil.fill(view: content, at: view) } } diff --git a/CXPopupKit/Updated/Popup/CXLifecycleAction.swift b/CXPopupKit/Updated/Popup/CXLifecycleAction.swift deleted file mode 100644 index 5d1d3c2..0000000 --- a/CXPopupKit/Updated/Popup/CXLifecycleAction.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// CXLifecycleAction.swift -// CXPopupKit -// -// Created by Cunqi Xiao on 3/13/18. -// Copyright © 2018 Cunqi Xiao. All rights reserved. -// - -import Foundation - -public protocol CXLifecycleAction { - func popupWindowDidLoad() - func popupWindowWillAppear() - func popupWindowDidAppear() - func popupWindowWillDisappear() - func popupWindowDidDisappear() -} - -public extension CXLifecycleAction { - func popupWindowDidLoad() {} - func popupWindowWillAppear() {} - func popupWindowDidAppear() {} - func popupWindowWillDisappear() {} - func popupWindowDidDisappear() {} -} diff --git a/CXPopupKit/Updated/Popup/CXPopup.swift b/CXPopupKit/Updated/Popup/CXPopup.swift deleted file mode 100644 index ee85215..0000000 --- a/CXPopupKit/Updated/Popup/CXPopup.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// CXPopup.swift -// CXPopupKit -// -// Created by Cunqi Xiao on 12/18/17. -// Copyright © 2017 Cunqi Xiao. All rights reserved. -// - -import UIKit -import SnapKit - -public typealias CXPopupHandler = (Any?) -> Void -public typealias CXPopupLifeCycleAction = () -> Void - -public final class CXPopup { - public var appearance: CXAppearance { - get { - return window.appearance - } - set { - window.appearance = newValue - } - } - - public var positiveAction: CXPopupHandler? { - get { - return window.positiveAction - } - set { - window.positiveAction = newValue - } - } - - public var negativeAction: CXPopupHandler? { - get { - return window.negativeAction - } - set { - window.negativeAction = newValue - } - } - - deinit { - print("CXPopup deinit") - } - - private var window = CXPopupWindow() - private var presentationController: CXPresentationController? - - public init(with popupable: CXPopupable, appearance: CXAppearance? = nil) { - window.content = popupable - window.appearance = appearance ?? CXAppearance() - window.holder = self - } - - public func show(at presenter: UIViewController?) { - presentationController = CXPresentationController(presentedViewController: window, presenting: presenter) - presentationController?.appearance = appearance - window.transitioningDelegate = presentationController - presenter?.present(window, animated: true, completion: nil) - } - - public func completeWithNegative(result: Any?) { - window.negativeAction?(result) - window.dismiss(animated: true, completion: nil) - } - - public func completeWithPositive(result: Any?) { - window.positiveAction?(result) - window.dismiss(animated: true, completion: nil) - } -} diff --git a/CXPopupKit/Updated/Popup/CXPopupWindow.swift b/CXPopupKit/Updated/Popup/CXPopupWindow.swift deleted file mode 100644 index b06f7d7..0000000 --- a/CXPopupKit/Updated/Popup/CXPopupWindow.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// CXPopupWindow.swift -// CXPopupKit -// -// Created by Cunqi Xiao on 3/8/18. -// Copyright © 2018 Cunqi Xiao. All rights reserved. -// - -import UIKit - -class CXPopupWindow: UIViewController { - override var shouldAutorotate: Bool { - return appearance.orientation.isAutoRotationEnabled - } - - override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - return appearance.orientation.supportedInterfaceOrientations - } - - override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { - return appearance.orientation.preferredInterfaceOrientationForPresentation - } - - var holder: CXPopup? - var appearance = CXAppearance() - var content: CXPopupable? - - var negativeAction: CXPopupHandler? - var positiveAction: CXPopupHandler? - - override func viewDidLoad() { - super.viewDidLoad() - setup() - content?.popupWindowDidLoad() - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - holder = nil - content?.popupWindowDidDisappear() - } - - private func setup() { - let window = appearance.window - view.backgroundColor = window.backgroundColor - - let strategy = getLayoutStrategy() - strategy.install((content as? UIView) ?? UIView(), into: view, basedOn: appearance) - } - - deinit { - print("CXPopupWindow deinit") - } - - private func getLayoutStrategy() -> CXPopupLayoutStrategy { - let w = appearance.window - - if !w.isSafeAreaEnabled { - return LayoutWithoutSafeAreaStrategy() - }else { - return LayoutWithOutsideSafeAreaStrategy() - } - } -} diff --git a/CXPopupKit/Updated/Popup/CXPopupable.swift b/CXPopupKit/Updated/Popup/CXPopupable.swift deleted file mode 100644 index 581041a..0000000 --- a/CXPopupKit/Updated/Popup/CXPopupable.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// CXPopupable.swift -// CXPopupKit -// -// Created by Cunqi Xiao on 3/8/18. -// Copyright © 2018 Cunqi Xiao. All rights reserved. -// - -import UIKit - -public protocol CXPopupable: CXLifecycleAction where Self: UIView { - var popup: CXPopup? { get } - func createPopup() -> CXPopup - func createPopup(_ appearance: CXAppearance) -> CXPopup -} - -extension CXPopupable { - public func createPopup() -> CXPopup { - return CXPopup(with: self, appearance: CXAppearance()) - } - - public func createPopup(_ appearance: CXAppearance) -> CXPopup { - return CXPopup(with: self, appearance: appearance) - } - - public var popup: CXPopup? { - var responder: UIResponder? = self - while responder != nil { - responder = responder?.next - if let window = responder as? CXPopupWindow { - return window.holder - } - } - return nil - } -} diff --git a/CXPopupKit/Updated/Popup/CXPresentationController.swift b/CXPopupKit/Updated/Popup/CXPresentationController.swift deleted file mode 100644 index bfe185f..0000000 --- a/CXPopupKit/Updated/Popup/CXPresentationController.swift +++ /dev/null @@ -1,138 +0,0 @@ -// -// CXPresentationController.swift -// CXPopupKit -// -// Created by Cunqi Xiao on 12/25/17. -// Copyright © 2017 Cunqi Xiao. All rights reserved. -// - -import UIKit - -final class CXPresentationController: UIPresentationController, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning { - var appearance = CXAppearance() - - private var presentationWrapperView: UIView? - private var backgroundView: UIView? - - override var frameOfPresentedViewInContainerView: CGRect { - return LayoutUtil.presentedViewSize(containerView!, appearance.window, appearance.window.isSafeAreaEnabled) - } - - override var presentedView: UIView? { - return presentationWrapperView - } - - override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) { - super.init(presentedViewController: presentedViewController, presenting: presentingViewController) - presentedViewController.modalPresentationStyle = .custom - } - - override func presentationTransitionWillBegin() { - setupPresentationWrapperView() - updatePresentedViewControllerView() - setupBackgroundView() - } - - override func presentationTransitionDidEnd(_ completed: Bool) { - if !completed { - self.presentationWrapperView = nil - self.backgroundView = nil - } - } - - override func dismissalTransitionWillBegin() { - let coordinator = self.presentingViewController.transitionCoordinator - coordinator?.animate(alongsideTransition: { [weak self] context in - self?.backgroundView?.alpha = 0 - }, completion: nil) - } - - override func dismissalTransitionDidEnd(_ completed: Bool) { - if !completed { - self.presentationWrapperView = nil - self.backgroundView = nil - } - } - - func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - guard let fromViewController = transitionContext?.viewController(forKey: .from) else { - return appearance.animation.duration.out - } - let isPresenting = fromViewController == self.presentingViewController - return isPresenting ? appearance.animation.duration.in : appearance.animation.duration.out - } - - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - let isPresenting = transitionContext.viewController(forKey: .from) == self.presentingViewController - let animation = appearance.animation.getAnimation(transitionContext, isPresenting) - animation.execute() - } - - override func containerViewWillLayoutSubviews() { - super.containerViewWillLayoutSubviews() - self.backgroundView?.frame = self.containerView!.bounds - updatePresentationWrapperViewFrame() - } - - func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { - return self - } - - func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return self - } - - func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return self - } - - private func updatePresentedViewControllerView() { - let presentedViewControllerView = super.presentedView! - let frame = CGRect(origin: .zero, size: frameOfPresentedViewInContainerView.size) - presentedViewControllerView.frame = frame - presentedViewControllerView.autoresizingMask = [.flexibleHeight, .flexibleWidth] - presentationWrapperView?.addSubview(presentedViewControllerView) - } - - private func setupPresentationWrapperView() { - presentationWrapperView = UIView() - presentationWrapperView?.backgroundColor = appearance.window.backgroundColor - updatePresentationWrapperViewFrame() - - if appearance.shadow.isEnabled { - presentationWrapperView?.layer.shadowColor = appearance.shadow.color.cgColor - presentationWrapperView?.layer.shadowOffset = appearance.shadow.offset - presentationWrapperView?.layer.shadowRadius = appearance.shadow.radius - presentationWrapperView?.layer.shadowOpacity = appearance.shadow.opacity - } - } - - private func updatePresentationWrapperViewFrame() { - presentationWrapperView?.frame = frameOfPresentedViewInContainerView - if appearance.window.enableInsideSafeArea { - presentationWrapperView?.frame = LayoutUtil.updateFrameForInsideSafeArea(frame: presentationWrapperView?.frame, at: containerView!, position: appearance.window.position) - } - } - - private func setupBackgroundView() { - backgroundView = UIView(frame: containerView!.bounds) - backgroundView?.backgroundColor = appearance.window.maskBackgroundColor - backgroundView?.alpha = appearance.window.maskBackgroundAlpha - backgroundView?.isOpaque = false - - if appearance.window.allowTouchOutsideToDismiss { - backgroundView?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(backgroundViewTapped))) - } - containerView?.addSubview(backgroundView!) - - let coordinator = self.presentingViewController.transitionCoordinator - backgroundView?.alpha = 0 - coordinator?.animate(alongsideTransition: { [unowned self] context in - self.backgroundView?.alpha = self.appearance.window.maskBackgroundAlpha - }, completion: nil) - } - - @objc private func backgroundViewTapped(gesture: UITapGestureRecognizer) { - presentingViewController.dismiss(animated: true, completion: nil) - } -} diff --git a/CXPopupKit/Updated/Popup/Layout/CXPopupLayoutStrategy.swift b/CXPopupKit/Updated/Popup/Layout/CXPopupLayoutStrategy.swift deleted file mode 100644 index 8e622b5..0000000 --- a/CXPopupKit/Updated/Popup/Layout/CXPopupLayoutStrategy.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// CXPopupLayoutStrategy.swift -// CXPopupKit -// -// Created by Cunqi Xiao on 3/8/18. -// Copyright © 2018 Cunqi Xiao. All rights reserved. -// - -import UIKit - -protocol CXPopupLayoutStrategy { - func install(_ content: UIView, into parent: UIView, basedOn appearance: CXAppearance) -} - -class LayoutWithoutSafeAreaStrategy: CXPopupLayoutStrategy { - func install(_ content: UIView, into parent: UIView, basedOn appearance: CXAppearance) { - LayoutUtil.fill(view: content, at: parent) - } -} - -class LayoutWithOutsideSafeAreaStrategy: CXPopupLayoutStrategy { - func install(_ content: UIView, into parent: UIView, basedOn appearance: CXAppearance) { - LayoutUtil.installWithSafeArea(view: content, into: parent, inset: .zero) - } -} diff --git a/CXPopupKit/Utils/LayoutUtil.swift b/CXPopupKit/Utils/LayoutUtil.swift index 9684d7d..feff618 100644 --- a/CXPopupKit/Utils/LayoutUtil.swift +++ b/CXPopupKit/Utils/LayoutUtil.swift @@ -7,37 +7,12 @@ import Foundation import SnapKit class LayoutUtil { - static func updateFrameForInsideSafeArea(frame: CGRect?, at container: UIView, position: CXAppearance.WindowPosition) -> CGRect { - let updatedFrame = frame ?? .zero - var inset: UIEdgeInsets = .zero - if #available(iOS 11.0, *) { - inset = container.safeAreaInsets - } else { - return updatedFrame - } - - switch position { - case .center: - return updatedFrame - case .top: - return CGRect(x: updatedFrame.origin.x, y: 0, width: updatedFrame.size.width, height: updatedFrame.size.height + inset.top) - case .bottom: - return CGRect(x: updatedFrame.origin.x, y: updatedFrame.origin.y - inset.bottom, width: updatedFrame.size.width, height: updatedFrame.size.height + inset.bottom) - case .left: - return CGRect(x: 0, y: updatedFrame.origin.y, width: updatedFrame.size.width + inset.left, height: updatedFrame.size.height) - case .right: - return CGRect(x: updatedFrame.origin.x - inset.right, y: updatedFrame.origin.y, width: updatedFrame.size.width + inset.right, height: updatedFrame.size.height) - case .custom(let offsetX, let offsetY): - return updatedFrame - } - } - static func getEstimateHeight(for string: NSString, with width: CGFloat, and font: UIFont) -> CGFloat { return string.boundingRect(with: CGSize(width: Double(width), height: Double.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil) - .size.height + .size.height } static func fill(view child: UIView, at parent: UIView) { @@ -47,77 +22,147 @@ class LayoutUtil { } } - static func presentedViewSize(_ containerView: UIView, _ window: CXAppearance.Window, _ isSafeAreaEnabled: Bool) -> CGRect { - let width = min(containerView.bounds.width, getLength(for: window.width, basedOn: containerView.bounds.width)) - let height = min(containerView.bounds.height, getLength(for: window.height, basedOn: containerView.bounds.height)) - let inset = getInset(from: containerView, and: window.margin, isSafeAreaEnabled: isSafeAreaEnabled) - let X = getWindowPositionX(position: window.position, containerWidth: containerView.bounds.width, targetWidth: width, inset: inset) - let Y = getWindowPositionY(position: window.position, containerHeight: containerView.bounds.height, targetHeight: height, inset: inset) - return CGRect(origin: CGPoint(x: X, y: Y), size: CGSize(width: width, height: height)) - } + static func presentedViewSize(`in` containerView: UIView, width: CXAppearance.WindowSize, height: CXAppearance.WindowSize, attached: CXAppearance.WindowPosition, isFollowingSafeAreaGuide: Bool, shouldFillOutSafeArea: Bool) -> CGRect { + let shouldFollowingSafeAreaGuide: Bool - private static func getInset(from containerView: UIView, and margin: UIEdgeInsets, isSafeAreaEnabled: Bool) -> UIEdgeInsets { - if #available(iOS 11.0, *), isSafeAreaEnabled { - let containerInset = containerView.safeAreaInsets - return UIEdgeInsets(top: containerInset.top + margin.top, left: containerInset.left + margin.left, bottom: containerInset.top + margin.top, right: containerInset.right + margin.right) + if isFollowingSafeAreaGuide { + shouldFollowingSafeAreaGuide = shouldFillOutSafeArea ? false : isFollowingSafeAreaGuide } else { - return margin + shouldFollowingSafeAreaGuide = false } + + var result = CGRect.zero + let targetInsets = convertWithSafeArea(isFollowingSafeAreaGuide: shouldFollowingSafeAreaGuide, at: containerView) + + let maximumWidth = containerView.bounds.width - targetInsets.left - targetInsets.right + let maximumHeight = containerView.bounds.height - targetInsets.top - targetInsets.bottom + + let wo = getWidthOffset(shouldFillOutSafeArea, targetInsets, at: attached) + let calculatedWidth = getWindowSize(size: width, for: containerView.bounds.width) + wo + let targetWidth = calculatedWidth > maximumWidth ? maximumWidth : calculatedWidth + let targetX = getWindowPositionX(position: attached, containerWidth: containerView.bounds.width, targetWidth: targetWidth, insetsLeft: targetInsets.left, insetsRight: targetInsets.right) + + let ho = getWidthOffset(shouldFillOutSafeArea, targetInsets, at: attached) + let calculatedHeight = getWindowSize(size: height, for: containerView.bounds.height) + ho + let targetHeight = calculatedHeight > maximumHeight ? maximumHeight : calculatedHeight + let targetY = getWindowPositionY(position: attached, containerHeight: containerView.bounds.height, targetHeight: targetHeight, insetTop: targetInsets.top, insetsBottom: targetInsets.bottom) + + result.origin = CGPoint(x: targetX, y: targetY) + result.size = CGSize(width: targetWidth, height: targetHeight) + return result } - static func getLength(for size: CXAppearance.WindowSize, basedOn length: CGFloat) -> CGFloat { - switch size { - case .equalToParent: - return length - case .partOfParent: - return length * size.adjustedValue - case .fixValue: - return size.adjustedValue + static func getWidthOffset(_ shouldFillOutSafeArea: Bool, _ insets: UIEdgeInsets, at position: CXAppearance.WindowPosition) -> CGFloat { + guard shouldFillOutSafeArea else { + return 0 + } + + switch position { + case .left: + return insets.left + case .right: + return insets.right + default: + return 0 } } + static func getHeightOffset(_ shouldFillOutSafeArea: Bool, _ insets: UIEdgeInsets, at position: CXAppearance.WindowPosition) -> CGFloat { + guard shouldFillOutSafeArea else { + return 0 + } + switch position { + case .top: + return insets.top + case .bottom: + return insets.bottom + default: + return 0 + } + } + + static func convertWithSafeArea(isFollowingSafeAreaGuide: Bool, at containerView: UIView?) -> UIEdgeInsets { + if #available(iOS 11.0, *), isFollowingSafeAreaGuide { + return containerView?.safeAreaInsets ?? .zero + } + return .zero + } + // X = L + x + R // X: ContainerWidth, L: insetsLeft, x: targetWidth, R: insetsRight - private static func getWindowPositionX(position: CXAppearance.WindowPosition, containerWidth: CGFloat, targetWidth: CGFloat, inset: UIEdgeInsets) -> CGFloat { + private static func getWindowPositionX(position: CXAppearance.WindowPosition, containerWidth: CGFloat, targetWidth: CGFloat, insetsLeft: CGFloat, insetsRight: CGFloat) -> CGFloat { switch position { - case .left: - return inset.left - case .right: - return containerWidth - inset.left - targetWidth - default: - return (containerWidth - targetWidth) / 2.0 + case .left: + return insetsLeft + case .right: + return containerWidth - insetsRight - targetWidth + default: + return (containerWidth - targetWidth) / 2.0 } } // Y = T + y + B // Y: ContainerHeight, T: insetsTop, y: targetHeight, B: insetsHeight - private static func getWindowPositionY(position: CXAppearance.WindowPosition, containerHeight: CGFloat, targetHeight: CGFloat, inset: UIEdgeInsets) -> CGFloat { + private static func getWindowPositionY(position: CXAppearance.WindowPosition, containerHeight: CGFloat, targetHeight: CGFloat, insetTop: CGFloat, insetsBottom: CGFloat) -> CGFloat { switch position { - case .top: - return inset.top - case .bottom: - return containerHeight - targetHeight - inset.bottom - default: - return (containerHeight - targetHeight) / 2.0 + case .top: + return insetTop + case .bottom: + return containerHeight - targetHeight - insetsBottom + default: + return (containerHeight - targetHeight) / 2.0 } } - - static func install(view: UIView, into parent: UIView, inset: UIEdgeInsets) { - parent.addSubview(view) - view.snp.makeConstraints { maker in - maker.edges.equalTo(parent).inset(inset) + private static func getWindowSize(size: CXAppearance.WindowSize, `for` length: CGFloat) -> CGFloat { + switch size { + case .equalToParent: + return length + case .partOfParent: + return length * size.adjustedValue + case .fixValue: + return size.adjustedValue } + } - static func installWithSafeArea(view: UIView, into parent: UIView, inset: UIEdgeInsets) { - if #available(iOS 11.0, *) { - parent.addSubview(view) - view.snp.makeConstraints { maker in - maker.edges.equalTo(parent.safeAreaLayoutGuide).inset(inset) + + static func install(view child: UIView, at parent: UIView, attached: CXAppearance.WindowPosition, insets: UIEdgeInsets, isFollowingSafeAreaGuide: Bool) { + parent.addSubview(child) + + child.snp.makeConstraints { maker in + switch attached { + case .left: + maker.top.bottom.trailing.equalTo(parent) + if #available(iOS 11.0, *), isFollowingSafeAreaGuide { + maker.leading.equalTo(parent.safeAreaLayoutGuide.snp.leading).offset(insets.left) + } else { + maker.leading.equalTo(parent).offset(insets.left) + } + case .right: + maker.top.bottom.leading.equalTo(parent) + if #available(iOS 11.0, *), isFollowingSafeAreaGuide { + maker.trailing.equalTo(parent.safeAreaLayoutGuide.snp.trailing).offset(insets.left) + } else { + maker.trailing.equalTo(parent).offset(-insets.right) + } + case .top: + maker.bottom.leading.trailing.equalTo(parent) + if #available(iOS 11.0, *), isFollowingSafeAreaGuide { + maker.top.equalTo(parent.safeAreaLayoutGuide.snp.top).offset(-insets.right) + } else { + maker.top.equalTo(parent).offset(insets.top) + } + case .bottom: + maker.top.leading.trailing.equalTo(parent) + if #available(iOS 11.0, *), isFollowingSafeAreaGuide { + maker.bottom.equalTo(parent.safeAreaLayoutGuide.snp.bottom).offset(-insets.bottom) + } else { + maker.bottom.equalTo(parent).offset(-insets.bottom) + } + default: + maker.leading.trailing.top.bottom.equalTo(parent) } - } else { - install(view: view, into: parent, inset: inset) } } } diff --git a/CXPopupKitDemo/Base.lproj/Main.storyboard b/CXPopupKitDemo/Base.lproj/Main.storyboard index 4734b2a..c192f2e 100644 --- a/CXPopupKitDemo/Base.lproj/Main.storyboard +++ b/CXPopupKitDemo/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -10,73 +10,32 @@ - - + + - - + + - - - - - - - - - - - - - - - - - + - + - - - - + + - + - - - - - + - - - - - - - - - - - - - - - - - - - diff --git a/CXPopupKitDemo/DemoListViewController.swift b/CXPopupKitDemo/DemoListViewController.swift deleted file mode 100644 index 5853c27..0000000 --- a/CXPopupKitDemo/DemoListViewController.swift +++ /dev/null @@ -1,250 +0,0 @@ -// -// DemoListViewController.swift -// CXPopupKitDemo -// -// Created by Cunqi Xiao on 3/13/18. -// Copyright © 2018 Cunqi Xiao. All rights reserved. -// - -import UIKit -import CXPopupKit - -class DemoListViewController: UIViewController { - @IBOutlet private weak var tableView: UITableView! - - private let items: [DemoItem] = [.basic, .roundedCorner, .slideMenu, .slideMenuWithSafeArea, .actionSheet, .basicSampleWithSafeAreaInside, .basicSampleWithoutSafeAreaButPadding, .alertView, .singleStringPicker, .singleCustomPicker, .datePicker] - private let cellIdentifier = "DemoItem" - - override func viewDidLoad() { - super.viewDidLoad() - - title = "Demo List" - tableView.tableFooterView = UIView() - tableView.rowHeight = UITableViewAutomaticDimension - tableView.dataSource = self - tableView.delegate = self - } -} - -extension DemoListViewController: UITableViewDataSource { - func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return items.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) - let item = items[indexPath.row] - cell.textLabel?.text = item.title - return cell - } -} - -extension DemoListViewController: UITableViewDelegate { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let item = items[indexPath.row] - switch item { - case .basic: - showBasicPopup() - case .roundedCorner: - showRoundedCornerPopup() - case .slideMenu: - showSlideMenuPopup() - case .slideMenuWithSafeArea: - showSlideMenuWithSafeAreaPopup() - case .actionSheet: - showActionSheetPopup() - case .basicSampleWithSafeAreaInside: - showBasicSampleWithSafeAreaInsidePopup() - case .basicSampleWithoutSafeAreaButPadding: - showBasicSampleWithoutSafeAreaButPadding() - case .alertView: - showAlertViewPopup() - case .singleStringPicker: - showSingleStringPicker() - case .singleCustomPicker: - showSingleCustomPicker() - case .datePicker: - showDatetimePicker() - } - } - - private func showBasicPopup() { - let basicSample = BasicSample() - basicSample.backgroundColor = .white - basicSample.appearance.window.width = .fixValue(size: 300) - basicSample.appearance.window.height = .partOfParent(percent: 0.35) - basicSample.appearance.window.position = .center - basicSample.appearance.animation.style = .bounceZoom - let popup = basicSample.createPopup() - DispatchQueue.main.async { - popup.show(at: self) - } - } - - private func showRoundedCornerPopup() { - let basicSample = BasicSample() - basicSample.backgroundColor = .white - basicSample.layer.cornerRadius = 9.0 - basicSample.layer.masksToBounds = true - basicSample.appearance.window.width = .fixValue(size: 300) - basicSample.appearance.window.height = .partOfParent(percent: 0.35) - basicSample.appearance.window.position = .center - basicSample.appearance.animation.style = .bounceZoom - basicSample.appearance.orientation.isAutoRotationEnabled = true - let popup = basicSample.createPopup() - DispatchQueue.main.async { - popup.show(at: self) - } - } - - private func showSlideMenuPopup() { - let menuSample = MenuSample() - menuSample.backgroundColor = .white - menuSample.appearance.window.isSafeAreaEnabled = false - let popup = menuSample.createPopup() - DispatchQueue.main.async { - popup.show(at: self) - } - } - - private func showSlideMenuWithSafeAreaPopup() { - let menuSample = MenuSample() - menuSample.backgroundColor = .white - menuSample.appearance.window.backgroundColor = .clear - menuSample.appearance.window.isSafeAreaEnabled = true - let popup = menuSample.createPopup() - DispatchQueue.main.async { - popup.show(at: self) - } - } - - private func showActionSheetPopup() { - let actionSheet = CXAlertView(type: .actionSheet, title: nil, message: "Pick a photo from library", cancel: "cancel", actions: ["OK"]) - DispatchQueue.main.async { - actionSheet.show(at: self) - } - } - - private func showAlertViewPopup() { - let actionSheet = CXAlertView(type: .alert, title: nil, message: "Pick a photo from library", cancel: "cancel", actions: ["OK"]) - DispatchQueue.main.async { - actionSheet.show(at: self) - } - } - - private func showBasicSampleWithSafeAreaInsidePopup() { - let basicSample = BasicSample() - basicSample.backgroundColor = .white - basicSample.appearance.window.width = .equalToParent - basicSample.appearance.window.height = .partOfParent(percent: 0.35) - basicSample.appearance.window.position = .bottom - basicSample.appearance.window.backgroundColor = .white - basicSample.appearance.animation.style = .fade - basicSample.appearance.window.isSafeAreaEnabled = true - basicSample.appearance.window.enableInsideSafeArea = true - let popup = basicSample.createPopup() - DispatchQueue.main.async { - popup.show(at: self) - } - } - - private func showBasicSampleWithoutSafeAreaButPadding() { - let basicSample = BasicSample() - basicSample.layer.cornerRadius = 32.0 - basicSample.layer.masksToBounds = true - basicSample.backgroundColor = .white - basicSample.appearance.window.width = .partOfParent(percent: 0.95) - basicSample.appearance.window.height = .partOfParent(percent: 0.35) - basicSample.appearance.window.position = .bottom - basicSample.appearance.animation.style = .fade - basicSample.appearance.window.isSafeAreaEnabled = true - basicSample.appearance.window.margin = UIEdgeInsets(top: 0, left: 8, bottom: 8, right: 8) - let popup = basicSample.createPopup() - DispatchQueue.main.async { - popup.show(at: self) - } - } - - private func showSingleStringPicker() { - let picker = CXPicker(with: ["Yes", "No", "I don't think so"], title: "isGoodEnough", cancelButtonText: "Cancel", doneButtonText: "Done") - DispatchQueue.main.async { - picker.createPopup().show(at: self) - } - } - - private func showSingleCustomPicker() { - let personA = Person(name: "Jack", age: 23) - let personB = Person(name: "Rose", age: 22) - let personC = Person(name: "Pi", age: 18) - let picker = CXPicker(with: [personA, personB, personC], title: "Character", cancelButtonText: "Cancel", doneButtonText: "Done") - let popup = picker.createPopup() - popup.positiveAction = { result in - guard let person = result as? Person else { - return - } - print(person.age) - } - DispatchQueue.main.async { - popup.show(at: self) - } - } - - private func showDatetimePicker() { - let picker = CXDatePicker(startDate: nil, mode: .dateAndTime, title: "Start Date", cancelButtonText: "Cancel", doneButtonText: "Done") - let popup = picker.createPopup() - popup.positiveAction = { result in - guard let date = result as? Date else { - return - } - print(date) - } - DispatchQueue.main.async { - popup.show(at: self) - } - } -} - -enum DemoItem { - case basic - case roundedCorner - case slideMenu - case slideMenuWithSafeArea - case actionSheet - case basicSampleWithSafeAreaInside - case basicSampleWithoutSafeAreaButPadding - case alertView - case singleStringPicker - case singleCustomPicker - case datePicker - - var title: String { - switch self { - case .basic: - return "Basic + (Plain Animation)" - case .roundedCorner: - return "Rounded corner + (Bounce zoom animation)" - case .slideMenu: - return "Slide menu (disable safe area)" - case .slideMenuWithSafeArea: - return "Slide menu (enable safe area)" - case .actionSheet: - return "Action sheet" - case .basicSampleWithSafeAreaInside: - return "Basic + (Safe area inside)" - case .basicSampleWithoutSafeAreaButPadding: - return "Basic + (Padding, no safe area)" - case .alertView: - return "Alert View" - case .singleStringPicker: - return "Single String Picker" - case .singleCustomPicker: - return "Single Custom Picker" - case .datePicker: - return "Date Picker" - } - } -} diff --git a/CXPopupKitDemo/Samples/BasicSample.swift b/CXPopupKitDemo/Samples/BasicSample.swift deleted file mode 100644 index 183c005..0000000 --- a/CXPopupKitDemo/Samples/BasicSample.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// BasicSample.swift -// CXPopupKitDemo -// -// Created by Cunqi Xiao on 3/13/18. -// Copyright © 2018 Cunqi Xiao. All rights reserved. -// - -import UIKit -import CXPopupKit - -class BasicSample: UIView { - var appearance = CXAppearance() -} - -extension BasicSample: CXPopupable { - func createPopup() -> CXPopup { - return CXPopup(with: self, appearance: appearance) - } -} diff --git a/CXPopupKitDemo/Samples/MenuSample.swift b/CXPopupKitDemo/Samples/MenuSample.swift deleted file mode 100644 index a985c0b..0000000 --- a/CXPopupKitDemo/Samples/MenuSample.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// MenuSample.swift -// CXPopupKitDemo -// -// Created by Cunqi Xiao on 3/13/18. -// Copyright © 2018 Cunqi Xiao. All rights reserved. -// - -import UIKit -import CXPopupKit - -class MenuSample: UIView { - var appearance = CXAppearance() -} - -extension MenuSample: CXPopupable { - func createPopup() -> CXPopup { - appearance.window.width = .fixValue(size: 300) - appearance.window.height = .equalToParent - appearance.window.position = .left - appearance.window.maskBackgroundColor = .clear - appearance.animation.duration = CXAnimation.Duration(0.35) - appearance.animation.style = .fade - appearance.animation.transition = CXAnimation.Transition(in: .right, out: .left) - return CXPopup(with: self, appearance: appearance) - } -} - diff --git a/CXPopupKitDemo/Samples/Person.swift b/CXPopupKitDemo/Samples/Person.swift deleted file mode 100644 index b475046..0000000 --- a/CXPopupKitDemo/Samples/Person.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Person.swift -// CXPopupKitDemo -// -// Created by Cunqi Xiao on 3/14/18. -// Copyright © 2018 Cunqi Xiao. All rights reserved. -// - -import Foundation -import CXPopupKit -struct Person: CXPickerExpressible { - let name: String - let age: Int - - func getCXPickerOptionName() -> String { - return name - } -} diff --git a/CXPopupKitDemo/ViewController.swift b/CXPopupKitDemo/ViewController.swift new file mode 100644 index 0000000..0a926eb --- /dev/null +++ b/CXPopupKitDemo/ViewController.swift @@ -0,0 +1,29 @@ +// +// ViewController.swift +// CXPopupKitDemo +// +// Created by Cunqi Xiao on 12/20/17. +// Copyright © 2017 Cunqi Xiao. All rights reserved. +// + +import UIKit +import CXPopupKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + @IBAction func didTapButton(_ sender: Any) { + let alertView = CXAlertView(type: .alert, title: "Hello", detail: "Apple introduced storyboard references in iOS 9 and macOS 10.11 with the goal of making storyboards less daunting and easier to manage. Storyboard references allow you to break a storyboard up into multiple, smaller storyboards. A storyboard reference ties multiple storyboards together, creating one, ...", cancel: "OK", actions: ["YES"]) + alertView.alertAppearance.color.backgroundColor = .white + alertView.show(at: self) + } +}