Adds ControllerInputsViewController to customize external game controller inputs
Includes necessary code changes to use refactored DeltaCore Input logic
This commit is contained in:
parent
c0b3a04110
commit
d70105e30e
@ -1 +1 @@
|
||||
Subproject commit caaf2e8e4bd847a2442382e121b07b4d5f139000
|
||||
Subproject commit f33f8bd91a9e0b41ba55b9aa8370397dd7e7f809
|
||||
@ -1 +1 @@
|
||||
Subproject commit 4eedbd481457e3c236d1f3ffab3ba2f49c6d18df
|
||||
Subproject commit 56401b9649f8abe971e3a66be1a3bb2cd984a1a0
|
||||
@ -1 +1 @@
|
||||
Subproject commit 0e622c7887e781363e36f6c7bfcd67fba3cb00ac
|
||||
Subproject commit 4e678e7eac511d342aab86522eede9fdb7b29108
|
||||
@ -1 +1 @@
|
||||
Subproject commit bdc95aeb5e7f50263c34187cbe38ddddb4a2576e
|
||||
Subproject commit 394627784bd0643c26e0fc95feafa5c960786a7c
|
||||
@ -30,6 +30,7 @@
|
||||
BF1173501DA32CF600047DF8 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */; };
|
||||
BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */; };
|
||||
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */; };
|
||||
BF15AF841F54B43B009B6AAB /* ActionInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF15AF831F54B43B009B6AAB /* ActionInput.swift */; };
|
||||
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */; };
|
||||
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1DAD5C1D9F576000E752A7 /* SystemControllerSkinsViewController.swift */; };
|
||||
BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2451BB73FE800CCF94A /* Assets.xcassets */; };
|
||||
@ -39,7 +40,7 @@
|
||||
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34FA101CF1899D006624C7 /* CheatTextView.swift */; };
|
||||
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF11C5D7FB000C1184C /* PauseViewController.swift */; };
|
||||
BF353FF61C5D837600C1184C /* PauseMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF353FF41C5D837600C1184C /* PauseMenu.storyboard */; };
|
||||
BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF81C5D870B00C1184C /* PauseItem.swift */; };
|
||||
BF353FF91C5D870B00C1184C /* MenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF81C5D870B00C1184C /* MenuItem.swift */; };
|
||||
BF353FFF1C5DA3C500C1184C /* PausePresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FFD1C5DA3C500C1184C /* PausePresentationController.swift */; };
|
||||
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */; };
|
||||
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */; };
|
||||
@ -68,6 +69,8 @@
|
||||
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */; };
|
||||
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */; };
|
||||
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF5E7F451B9A652600AE44F8 /* Settings.storyboard */; };
|
||||
BF6424831F5B8F3F00D6AB44 /* ListMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6424821F5B8F3F00D6AB44 /* ListMenuViewController.swift */; };
|
||||
BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */; };
|
||||
BF6866171DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */; };
|
||||
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF696B7F1D9B2B02009639E0 /* Theme.swift */; };
|
||||
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3121EB7E47F008E83CD /* ImportOption.swift */; };
|
||||
@ -79,8 +82,11 @@
|
||||
BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; };
|
||||
BF70798D1B6B464B0019077C /* ZipZap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */; };
|
||||
BF7AE8081C2E858400B1B5BC /* PauseMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */; };
|
||||
BF7AE8081C2E858400B1B5BC /* GridMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */; };
|
||||
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */; };
|
||||
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF616A121F08184A0077F8B2 /* ControllerInputsViewController.swift */; };
|
||||
BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */; };
|
||||
BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */; };
|
||||
BF930FFD1EB6D6FF00E8DBA0 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF930FFC1EB6D6FF00E8DBA0 /* System.swift */; };
|
||||
BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2761E4977BF0030E7AD /* GameMetadata.swift */; };
|
||||
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */; };
|
||||
@ -92,10 +98,12 @@
|
||||
BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63BDE91D389EEB00FCB040 /* GameViewController.swift */; };
|
||||
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FEC1B8AA4FA00495943 /* Settings.swift */; };
|
||||
BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAB2E21EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift */; };
|
||||
BFC6F7B81F435BC500221B96 /* Input+Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC6F7B71F435BC500221B96 /* Input+Display.swift */; };
|
||||
BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */; };
|
||||
BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */; };
|
||||
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */; };
|
||||
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */; };
|
||||
BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */; };
|
||||
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */; };
|
||||
BFEC732D1AAECC4A00650035 /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; };
|
||||
BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@ -144,6 +152,7 @@
|
||||
BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllersSettingsViewController.swift; path = Controllers/ControllersSettingsViewController.swift; sourceTree = "<group>"; };
|
||||
BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewGameViewController.swift; sourceTree = "<group>"; };
|
||||
BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EmulatorCore+Cheats.swift"; sourceTree = "<group>"; };
|
||||
BF15AF831F54B43B009B6AAB /* ActionInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionInput.swift; sourceTree = "<group>"; };
|
||||
BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Importing.swift"; sourceTree = "<group>"; };
|
||||
BF1DAD5C1D9F576000E752A7 /* SystemControllerSkinsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SystemControllerSkinsViewController.swift; path = "Controller Skins/SystemControllerSkinsViewController.swift"; sourceTree = "<group>"; };
|
||||
BF27CC861BC9E3C600A20D89 /* Delta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Delta.entitlements; sourceTree = "<group>"; };
|
||||
@ -155,7 +164,7 @@
|
||||
BF34FA101CF1899D006624C7 /* CheatTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatTextView.swift; path = "Pause Menu/Cheats/CheatTextView.swift"; sourceTree = "<group>"; };
|
||||
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseViewController.swift; path = "Pause Menu/PauseViewController.swift"; sourceTree = "<group>"; };
|
||||
BF353FF51C5D837600C1184C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PauseMenu.storyboard; sourceTree = "<group>"; };
|
||||
BF353FF81C5D870B00C1184C /* PauseItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseItem.swift; path = "Pause Menu/PauseItem.swift"; sourceTree = "<group>"; };
|
||||
BF353FF81C5D870B00C1184C /* MenuItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MenuItem.swift; path = "Pause Menu/MenuItem.swift"; sourceTree = "<group>"; };
|
||||
BF353FFD1C5DA3C500C1184C /* PausePresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PausePresentationController.swift; path = "Pause Menu/Presentation Controller/PausePresentationController.swift"; sourceTree = "<group>"; };
|
||||
BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = PausePresentationControllerContentView.xib; path = "Pause Menu/Presentation Controller/PausePresentationControllerContentView.xib"; sourceTree = "<group>"; };
|
||||
BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseStoryboardSegue.swift; path = "Pause Menu/Segues/PauseStoryboardSegue.swift"; sourceTree = "<group>"; };
|
||||
@ -187,7 +196,10 @@
|
||||
BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Conveniences.swift"; sourceTree = "<group>"; };
|
||||
BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
||||
BF5E7F451B9A652600AE44F8 /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = "<group>"; };
|
||||
BF616A121F08184A0077F8B2 /* ControllerInputsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ControllerInputsViewController.swift; path = Controllers/ControllerInputsViewController.swift; sourceTree = "<group>"; };
|
||||
BF63BDE91D389EEB00FCB040 /* GameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = "<group>"; };
|
||||
BF6424821F5B8F3F00D6AB44 /* ListMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ListMenuViewController.swift; path = "Components/Popover Menu/ListMenuViewController.swift"; sourceTree = "<group>"; };
|
||||
BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+ParentViewController.swift"; sourceTree = "<group>"; };
|
||||
BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ControllerSkin+Configuring.swift"; sourceTree = "<group>"; };
|
||||
BF696B7F1D9B2B02009639E0 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Theme.swift; path = Theming/Theme.swift; sourceTree = "<group>"; };
|
||||
BF6BB2451BB73FE800CCF94A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
@ -199,8 +211,10 @@
|
||||
BF6BF3261EB87EB8008E83CD /* PhotoLibraryImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PhotoLibraryImportOption.swift; path = "Importing/Import Options/PhotoLibraryImportOption.swift"; sourceTree = "<group>"; };
|
||||
BF70798B1B6B464B0019077C /* ZipZap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZipZap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = "<group>"; };
|
||||
BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseMenuViewController.swift; path = "Pause Menu/PauseMenuViewController.swift"; sourceTree = "<group>"; };
|
||||
BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridMenuViewController.swift; path = "Pause Menu/GridMenuViewController.swift"; sourceTree = "<group>"; };
|
||||
BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Delta.swift"; sourceTree = "<group>"; };
|
||||
BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PopoverMenuController.swift; path = "Components/Popover Menu/PopoverMenuController.swift"; sourceTree = "<group>"; };
|
||||
BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InputCalloutView.swift; path = Controllers/InputCalloutView.swift; sourceTree = "<group>"; };
|
||||
BF930FFC1EB6D6FF00E8DBA0 /* System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = System.swift; path = Systems/System.swift; sourceTree = "<group>"; };
|
||||
BF95E2761E4977BF0030E7AD /* GameMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameMetadata.swift; path = Database/OpenVGDB/GameMetadata.swift; sourceTree = "<group>"; };
|
||||
BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabaseBrowserViewController.swift; path = Database/OpenVGDB/GamesDatabaseBrowserViewController.swift; sourceTree = "<group>"; };
|
||||
@ -209,9 +223,11 @@
|
||||
BFAA1FEC1B8AA4FA00495943 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
BFBAB2E21EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DeltaCoreProtocol+Delta.swift"; sourceTree = "<group>"; };
|
||||
BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SNESDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFC6F7B71F435BC500221B96 /* Input+Display.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Input+Display.swift"; sourceTree = "<group>"; };
|
||||
BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatsViewController.swift; path = "Pause Menu/Cheats/CheatsViewController.swift"; sourceTree = "<group>"; };
|
||||
BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewControllerContextTransitioning+Conveniences.swift"; sourceTree = "<group>"; };
|
||||
BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollectionViewController.swift; sourceTree = "<group>"; };
|
||||
BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PopoverMenuButton.swift; path = "Components/Popover Menu/PopoverMenuButton.swift"; sourceTree = "<group>"; };
|
||||
BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesStoryboardSegue.swift; path = Segues/SaveStatesStoryboardSegue.swift; sourceTree = "<group>"; };
|
||||
BFEC732C1AAECC4A00650035 /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBCDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@ -262,6 +278,8 @@
|
||||
BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */,
|
||||
BFBAB2E21EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift */,
|
||||
BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */,
|
||||
BFC6F7B71F435BC500221B96 /* Input+Display.swift */,
|
||||
BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@ -270,6 +288,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */,
|
||||
BF616A121F08184A0077F8B2 /* ControllerInputsViewController.swift */,
|
||||
BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */,
|
||||
);
|
||||
name = Controllers;
|
||||
sourceTree = "<group>";
|
||||
@ -316,6 +336,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF5942581E09BB810051894B /* Action.swift */,
|
||||
BFE0229C1F5B56840052D888 /* Popover Menu */,
|
||||
BF5942671E09BBB70051894B /* Collection View */,
|
||||
BF5942601E09BBA80051894B /* Loading */,
|
||||
);
|
||||
@ -428,8 +449,8 @@
|
||||
children = (
|
||||
BF353FF41C5D837600C1184C /* PauseMenu.storyboard */,
|
||||
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */,
|
||||
BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */,
|
||||
BF353FF81C5D870B00C1184C /* PauseItem.swift */,
|
||||
BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */,
|
||||
BF353FF81C5D870B00C1184C /* MenuItem.swift */,
|
||||
BF3540031C5DA6D800C1184C /* Save States */,
|
||||
BFC9B7371CEFCD08008629BB /* Cheats */,
|
||||
BF353FFB1C5DA2F600C1184C /* Presentation Controller */,
|
||||
@ -508,6 +529,16 @@
|
||||
name = Cheats;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFE0229C1F5B56840052D888 /* Popover Menu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */,
|
||||
BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */,
|
||||
BF6424821F5B8F3F00D6AB44 /* ListMenuViewController.swift */,
|
||||
);
|
||||
name = "Popover Menu";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFEC732F1AAECCBD00650035 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -521,8 +552,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BFFA71D91AAC406100EE9DD1 /* Delta */,
|
||||
BFEC732F1AAECCBD00650035 /* Resources */,
|
||||
BF9F4FCD1AAD7B25004C9500 /* Frameworks */,
|
||||
BFEC732F1AAECCBD00650035 /* Resources */,
|
||||
BFFA71D81AAC406100EE9DD1 /* Products */,
|
||||
FD1E8AE87FA2DB8793F7B937 /* Pods */,
|
||||
);
|
||||
@ -572,6 +603,7 @@
|
||||
children = (
|
||||
BF63BDE91D389EEB00FCB040 /* GameViewController.swift */,
|
||||
BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */,
|
||||
BF15AF831F54B43B009B6AAB /* ActionInput.swift */,
|
||||
);
|
||||
path = Emulation;
|
||||
sourceTree = "<group>";
|
||||
@ -776,6 +808,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
BFC6F7B81F435BC500221B96 /* Input+Display.swift in Sources */,
|
||||
BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */,
|
||||
BF6BF3181EB82111008E83CD /* iTunesImportOption.swift in Sources */,
|
||||
BF59427C1E09BC830051894B /* Cheat.swift in Sources */,
|
||||
@ -789,24 +822,29 @@
|
||||
BF59428E1E09BCFB0051894B /* ImportController.swift in Sources */,
|
||||
BF930FFD1EB6D6FF00E8DBA0 /* System.swift in Sources */,
|
||||
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */,
|
||||
BF6424831F5B8F3F00D6AB44 /* ListMenuViewController.swift in Sources */,
|
||||
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */,
|
||||
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */,
|
||||
BFFC46201D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift in Sources */,
|
||||
BF5942731E09BC700051894B /* Model.xcdatamodel in Sources */,
|
||||
BF15AF841F54B43B009B6AAB /* ActionInput.swift in Sources */,
|
||||
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */,
|
||||
BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */,
|
||||
BF353FF91C5D870B00C1184C /* MenuItem.swift in Sources */,
|
||||
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */,
|
||||
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
|
||||
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */,
|
||||
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
|
||||
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */,
|
||||
BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */,
|
||||
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */,
|
||||
BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */,
|
||||
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
|
||||
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */,
|
||||
BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */,
|
||||
BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */,
|
||||
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */,
|
||||
BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */,
|
||||
BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */,
|
||||
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */,
|
||||
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */,
|
||||
BF5942881E09BC8B0051894B /* _Game.swift in Sources */,
|
||||
@ -819,6 +857,7 @@
|
||||
BF5942941E09BD1A0051894B /* NSManagedObject+Conveniences.swift in Sources */,
|
||||
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */,
|
||||
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
|
||||
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */,
|
||||
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */,
|
||||
BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */,
|
||||
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */,
|
||||
@ -835,13 +874,14 @@
|
||||
BF99A5971DC2F9C400468E9E /* ControllerSkinTableViewCell.swift in Sources */,
|
||||
BF5942861E09BC8B0051894B /* _Cheat.swift in Sources */,
|
||||
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */,
|
||||
BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */,
|
||||
BF5942931E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m in Sources */,
|
||||
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */,
|
||||
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */,
|
||||
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */,
|
||||
BF59425C1E09BB810051894B /* Action.swift in Sources */,
|
||||
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */,
|
||||
BF7AE8081C2E858400B1B5BC /* PauseMenuViewController.swift in Sources */,
|
||||
BF7AE8081C2E858400B1B5BC /* GridMenuViewController.swift in Sources */,
|
||||
BF6BF31A1EB82146008E83CD /* ClipboardImportOption.swift in Sources */,
|
||||
BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */,
|
||||
BF59427F1E09BC830051894B /* GameCollection.swift in Sources */,
|
||||
|
||||
@ -40,7 +40,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate
|
||||
}
|
||||
|
||||
// Controllers
|
||||
ExternalControllerManager.shared.startMonitoringExternalControllers()
|
||||
ExternalGameControllerManager.shared.startMonitoring()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11185.3" systemVersion="15G31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Dt0-nV-isV">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.19" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Dt0-nV-isV">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11151.4"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.16"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<customFonts key="customFonts">
|
||||
<array key="Menlo.ttc">
|
||||
<string>Menlo-Regular</string>
|
||||
</array>
|
||||
</customFonts>
|
||||
<scenes>
|
||||
<!--Pause View Controller-->
|
||||
<scene sceneID="Wst-Dv-TjM">
|
||||
@ -16,15 +20,17 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="gF0-0U-kR7"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="oOH-ea-jcb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="p2M-dE-BJs" userLabel="Blur View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="eyD-0d-RHe" userLabel="Blur Content View">
|
||||
<frame key="frameInset"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rqN-NB-jbb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<connections>
|
||||
<segue destination="sWv-Ky-VGs" kind="embed" identifier="embedNavigationController" id="1Ja-XW-uoT"/>
|
||||
</connections>
|
||||
@ -63,7 +69,7 @@
|
||||
<objects>
|
||||
<navigationController id="sWv-Ky-VGs" sceneMemberID="viewController">
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" barStyle="black" prompted="NO"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="Snh-Z0-9kC">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" misplaced="YES" barStyle="black" id="Snh-Z0-9kC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
@ -78,9 +84,9 @@
|
||||
<!--Paused-->
|
||||
<scene sceneID="1md-hu-g0J">
|
||||
<objects>
|
||||
<collectionViewController id="0jA-NY-mvB" customClass="PauseMenuViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" delaysContentTouches="NO" dataMode="prototypes" id="scc-uc-vaJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<collectionViewController id="0jA-NY-mvB" customClass="GridMenuViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" misplaced="YES" delaysContentTouches="NO" dataMode="prototypes" id="scc-uc-vaJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="10" id="yXv-zl-idO" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
|
||||
@ -91,7 +97,7 @@
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="6XS-Ne-nGZ" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<frame key="frameInset" minY="84" width="60" height="80"/>
|
||||
<rect key="frame" x="0.0" y="20" width="60" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="80"/>
|
||||
@ -128,7 +134,7 @@
|
||||
<objects>
|
||||
<collectionViewController storyboardIdentifier="saveStatesViewController" id="OOk-k7-INg" customClass="SaveStatesViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" id="XgF-OF-CVf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="tvW-q1-PD8" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
|
||||
@ -139,7 +145,7 @@
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="c3N-1A-ryV" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<frame key="frameInset" minX="20" minY="124" width="50" height="50"/>
|
||||
<rect key="frame" x="20" y="60" width="50" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
@ -148,7 +154,7 @@
|
||||
</collectionViewCell>
|
||||
</cells>
|
||||
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Header" id="YeY-W9-CC6" customClass="SaveStatesCollectionHeaderView" customModule="Delta" customModuleProvider="target">
|
||||
<frame key="frameInset" minY="64" width="375" height="50"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</collectionReusableView>
|
||||
<connections>
|
||||
@ -174,22 +180,22 @@
|
||||
<objects>
|
||||
<tableViewController id="wb8-5o-1jE" customClass="CheatsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="f5S-hV-1yV">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="e8g-ZW-5lQ" customClass="CheatTableViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<frame key="frameInset" minY="92" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="28" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="e8g-ZW-5lQ" id="AHE-Jk-ULE">
|
||||
<frame key="frameInset" width="375" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="emc-gw-KkJ" userLabel="Selected Background View">
|
||||
<frame key="frameInset" maxY="-0.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" id="9bA-Tg-Bko">
|
||||
<frame key="frameInset"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
@ -198,8 +204,9 @@
|
||||
</vibrancyEffect>
|
||||
</visualEffectView>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="R4A-9d-DGb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="EdX-fU-x54">
|
||||
<frame key="frameInset"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
<vibrancyEffect>
|
||||
@ -245,20 +252,21 @@
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="editCheatViewController" id="jTR-Oe-YUJ" customClass="EditCheatViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="BV9-ff-x83">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<sections>
|
||||
<tableViewSection headerTitle="Name" id="QT6-DZ-g70">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="ZeC-rg-QFa">
|
||||
<rect key="frame" x="0.0" y="119.5" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="55.5" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="ZeC-rg-QFa" id="UIF-fK-ApW">
|
||||
<frame key="frameInset" width="375" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Cheat Name" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="DD1-X0-hg7">
|
||||
<rect key="frame" x="20" y="0.0" width="560" height="43.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="words" autocorrectionType="no" returnKeyType="done"/>
|
||||
<connections>
|
||||
@ -280,13 +288,14 @@
|
||||
<tableViewSection headerTitle="Type" footerTitle="Description" id="rvn-VK-2uH">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Tst-zn-e04">
|
||||
<rect key="frame" x="0.0" y="227" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="163" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="Tst-zn-e04" id="gwV-zS-RWQ">
|
||||
<frame key="frameInset" width="375" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="xrD-ue-96Q">
|
||||
<rect key="frame" x="20" y="8" width="560" height="29"/>
|
||||
<segments>
|
||||
<segment title="First"/>
|
||||
<segment title="Second"/>
|
||||
@ -308,13 +317,14 @@
|
||||
<tableViewSection headerTitle="Code" footerTitle="Description" id="rHC-nA-ga0">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="210" id="xxc-cz-sb7">
|
||||
<rect key="frame" x="0.0" y="346.5" width="375" height="210"/>
|
||||
<rect key="frame" x="0.0" y="282.5" width="600" height="210"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="xxc-cz-sb7" id="agU-SE-fNa">
|
||||
<frame key="frameInset" width="375" height="209.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="209.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" text="XXXXXXXX YYYYYYYY" translatesAutoresizingMaskIntoConstraints="NO" id="a17-LB-QXD" customClass="CheatTextView" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="209.5"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="26"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="allCharacters" autocorrectionType="no" spellCheckingType="no" returnKeyType="done"/>
|
||||
|
||||
64
Delta/Components/Popover Menu/ListMenuViewController.swift
Normal file
64
Delta/Components/Popover Menu/ListMenuViewController.swift
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// PopoverMenuViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/2/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import Roxas
|
||||
|
||||
class ListMenuViewController: UITableViewController
|
||||
{
|
||||
var items: [MenuItem] {
|
||||
get { return self.dataSource.items }
|
||||
set { self.dataSource.items = newValue }
|
||||
}
|
||||
|
||||
fileprivate let dataSource = RSTArrayTableViewDataSource<MenuItem>(items: [])
|
||||
|
||||
override var preferredContentSize: CGSize {
|
||||
get {
|
||||
let navigationBarHeight = self.navigationController?.navigationBar.bounds.height ?? 0.0
|
||||
return CGSize(width: 0, height: (self.tableView.rowHeight * CGFloat(self.items.count)) + navigationBarHeight)
|
||||
}
|
||||
set {}
|
||||
}
|
||||
|
||||
init()
|
||||
{
|
||||
super.init(style: .plain)
|
||||
|
||||
self.dataSource.cellConfigurationHandler = { (cell, item, indexPath) in
|
||||
cell.textLabel?.text = item.text
|
||||
cell.accessoryType = item.isSelected ? .checkmark : .none
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.tableView.dataSource = self.dataSource
|
||||
self.tableView.rowHeight = 44
|
||||
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: RSTCellContentGenericCellIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
extension ListMenuViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||
{
|
||||
let item = self.dataSource.item(at: indexPath)
|
||||
item.isSelected = !item.isSelected
|
||||
item.action(item)
|
||||
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
121
Delta/Components/Popover Menu/PopoverMenuButton.swift
Normal file
121
Delta/Components/Popover Menu/PopoverMenuButton.swift
Normal file
@ -0,0 +1,121 @@
|
||||
//
|
||||
// PopoverMenuButton.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/2/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UINavigationBar
|
||||
{
|
||||
fileprivate var defaultTitleTextAttributes: [String: Any]? {
|
||||
if let textAttributes = self._defaultTitleTextAttributes
|
||||
{
|
||||
return textAttributes
|
||||
}
|
||||
|
||||
// Make "copy" of self.
|
||||
let navigationBar = UINavigationBar(frame: .zero)
|
||||
navigationBar.barStyle = self.barStyle
|
||||
|
||||
// Set item with title so we can retrieve default text attributes.
|
||||
let navigationItem = UINavigationItem(title: "Testut")
|
||||
navigationBar.items = [navigationItem]
|
||||
navigationBar.isHidden = true
|
||||
|
||||
// Must be added to window hierarchy for it to create title UILabel.
|
||||
self.addSubview(navigationBar)
|
||||
defer { navigationBar.removeFromSuperview() }
|
||||
|
||||
navigationBar.layoutIfNeeded()
|
||||
|
||||
let textAttributes = navigationBar._defaultTitleTextAttributes
|
||||
return textAttributes
|
||||
}
|
||||
|
||||
fileprivate var _defaultTitleTextAttributes: [String: Any]? {
|
||||
guard self.titleTextAttributes == nil else { return self.titleTextAttributes }
|
||||
|
||||
guard
|
||||
let contentView = self.subviews.first(where: { NSStringFromClass(type(of: $0)).contains("ContentView") || NSStringFromClass(type(of: $0)).contains("ItemView") }),
|
||||
let titleLabel = contentView.subviews.first(where: { $0 is UILabel }) as? UILabel
|
||||
else { return nil }
|
||||
|
||||
let textAttributes = titleLabel.attributedText?.attributes(at: 0, effectiveRange: nil)
|
||||
return textAttributes
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverMenuButton: UIControl
|
||||
{
|
||||
var title: String {
|
||||
get { return self.textLabel.text ?? "" }
|
||||
set {
|
||||
self.textLabel.text = newValue
|
||||
self.updateTextAttributes()
|
||||
self.invalidateIntrinsicContentSize()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let textLabel: UILabel
|
||||
fileprivate let arrowLabel: UILabel
|
||||
fileprivate let stackView: UIStackView
|
||||
|
||||
fileprivate var parentNavigationBar: UINavigationBar? {
|
||||
guard let navigationController = self.parentViewController as? UINavigationController ?? self.parentViewController?.navigationController else { return nil }
|
||||
guard self.isDescendant(of: navigationController.navigationBar) else { return nil }
|
||||
|
||||
return navigationController.navigationBar
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
return self.stackView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
|
||||
}
|
||||
|
||||
init()
|
||||
{
|
||||
self.textLabel = UILabel()
|
||||
self.textLabel.textColor = .black
|
||||
|
||||
self.arrowLabel = UILabel()
|
||||
self.arrowLabel.text = "▾"
|
||||
self.arrowLabel.textColor = .black
|
||||
|
||||
self.stackView = UIStackView(arrangedSubviews: [self.textLabel, self.arrowLabel])
|
||||
self.stackView.axis = .horizontal
|
||||
self.stackView.distribution = .fillProportionally
|
||||
self.stackView.alignment = .center
|
||||
self.stackView.spacing = 4.0
|
||||
self.stackView.isUserInteractionEnabled = false
|
||||
|
||||
let intrinsicContentSize = self.stackView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
|
||||
super.init(frame: CGRect(origin: .zero, size: intrinsicContentSize))
|
||||
|
||||
self.addSubview(self.stackView, pinningEdgesWith: .zero)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func didMoveToSuperview()
|
||||
{
|
||||
self.updateTextAttributes()
|
||||
}
|
||||
}
|
||||
|
||||
private extension PopoverMenuButton
|
||||
{
|
||||
func updateTextAttributes()
|
||||
{
|
||||
guard let parentNavigationBar = self.parentNavigationBar else { return }
|
||||
guard let textAttributes = parentNavigationBar.defaultTitleTextAttributes else { return }
|
||||
|
||||
for label in [self.textLabel, self.arrowLabel]
|
||||
{
|
||||
label.attributedText = NSAttributedString(string: label.text ?? "", attributes: textAttributes)
|
||||
}
|
||||
}
|
||||
}
|
||||
102
Delta/Components/Popover Menu/PopoverMenuController.swift
Normal file
102
Delta/Components/Popover Menu/PopoverMenuController.swift
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
// PopoverMenuController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/5/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
private var popoverMenuControllerKey: UInt8 = 0
|
||||
|
||||
extension UINavigationItem
|
||||
{
|
||||
var popoverMenuController: PopoverMenuController? {
|
||||
get { return objc_getAssociatedObject(self, &popoverMenuControllerKey) as? PopoverMenuController }
|
||||
set {
|
||||
self.titleView = newValue?.popoverMenuButton
|
||||
objc_setAssociatedObject(self, &popoverMenuControllerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverMenuController: NSObject
|
||||
{
|
||||
let popoverViewController: UIViewController
|
||||
|
||||
let popoverMenuButton: PopoverMenuButton
|
||||
|
||||
var isActive: Bool = false {
|
||||
willSet {
|
||||
guard newValue != self.isActive else { return }
|
||||
|
||||
if newValue
|
||||
{
|
||||
self.presentPopoverViewController()
|
||||
}
|
||||
else
|
||||
{
|
||||
self.dismissPopoverViewController()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(popoverViewController: UIViewController)
|
||||
{
|
||||
self.popoverViewController = popoverViewController
|
||||
|
||||
self.popoverMenuButton = PopoverMenuButton()
|
||||
|
||||
super.init()
|
||||
|
||||
self.popoverMenuButton.addTarget(self, action: #selector(PopoverMenuController.pressedPopoverMenuButton(_:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private extension PopoverMenuController
|
||||
{
|
||||
@objc func pressedPopoverMenuButton(_ button: PopoverMenuButton)
|
||||
{
|
||||
self.isActive = !self.isActive
|
||||
}
|
||||
|
||||
func presentPopoverViewController()
|
||||
{
|
||||
guard !self.isActive else { return }
|
||||
|
||||
guard let presentingViewController = self.popoverMenuButton.parentViewController else { return }
|
||||
|
||||
self.popoverViewController.modalPresentationStyle = .popover
|
||||
self.popoverViewController.popoverPresentationController?.delegate = self
|
||||
self.popoverViewController.popoverPresentationController?.sourceView = self.popoverMenuButton.superview
|
||||
self.popoverViewController.popoverPresentationController?.sourceRect = self.popoverMenuButton.frame
|
||||
|
||||
presentingViewController.present(self.popoverViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func dismissPopoverViewController()
|
||||
{
|
||||
guard self.isActive else { return }
|
||||
|
||||
self.popoverViewController.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension PopoverMenuController: UIPopoverPresentationControllerDelegate
|
||||
{
|
||||
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle
|
||||
{
|
||||
// Force popover presentation, regardless of trait collection.
|
||||
return .none
|
||||
}
|
||||
|
||||
func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController)
|
||||
{
|
||||
self.isActive = false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
//
|
||||
// PopoverMenuViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/2/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import Roxas
|
||||
|
||||
class ListMenuViewController: UITableViewController
|
||||
{
|
||||
var items: [MenuItem] {
|
||||
get { return self.dataSource.items }
|
||||
set { self.dataSource.items = newValue; }
|
||||
}
|
||||
|
||||
fileprivate let dataSource = RSTArrayTableViewDataSource<MenuItem>(items: [])
|
||||
|
||||
override var preferredContentSize: CGSize {
|
||||
get {
|
||||
let navigationBarHeight = self.navigationController?.navigationBar.bounds.height ?? 0.0
|
||||
return CGSize(width: 0, height: (self.tableView.rowHeight * CGFloat(self.items.count)) + navigationBarHeight)
|
||||
}
|
||||
set {}
|
||||
}
|
||||
|
||||
init()
|
||||
{
|
||||
super.init(style: .plain)
|
||||
|
||||
self.dataSource.cellConfigurationHandler = { (cell, item, indexPath) in
|
||||
cell.textLabel?.text = item.text
|
||||
cell.accessoryType = item.isSelected ? .checkmark : .none
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.tableView.dataSource = self.dataSource
|
||||
self.tableView.rowHeight = 44
|
||||
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: RSTCellContentGenericCellIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
extension ListMenuViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||
{
|
||||
self.items.forEach { $0.isSelected = false }
|
||||
|
||||
let item = self.dataSource.item(at: indexPath)
|
||||
item.isSelected = true
|
||||
item.action(item)
|
||||
|
||||
self.tableView.reloadData()
|
||||
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@ -88,9 +88,9 @@ extension ControllerSkin: ControllerSkinProtocol
|
||||
return self.controllerSkin?.image(for: traits, preferredSize: preferredSize)
|
||||
}
|
||||
|
||||
public func inputs(for traits: DeltaCore.ControllerSkin.Traits, point: CGPoint) -> [Input]?
|
||||
public func inputs(for traits: DeltaCore.ControllerSkin.Traits, at point: CGPoint) -> [Input]?
|
||||
{
|
||||
return self.controllerSkin?.inputs(for: traits, point: point)
|
||||
return self.controllerSkin?.inputs(for: traits, at: point)
|
||||
}
|
||||
|
||||
public func items(for traits: DeltaCore.ControllerSkin.Traits) -> [DeltaCore.ControllerSkin.Item]?
|
||||
|
||||
28
Delta/Emulation/ActionInput.swift
Normal file
28
Delta/Emulation/ActionInput.swift
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// ActionInput.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 8/28/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import DeltaCore
|
||||
|
||||
public extension GameControllerInputType
|
||||
{
|
||||
static let action = GameControllerInputType("com.rileytestut.Delta.input.action")
|
||||
}
|
||||
|
||||
enum ActionInput: String
|
||||
{
|
||||
case saveState
|
||||
case loadState
|
||||
case fastForward
|
||||
}
|
||||
|
||||
extension ActionInput: Input
|
||||
{
|
||||
var type: InputType {
|
||||
return .controller(.action)
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,22 @@ private extension GameViewController
|
||||
self.gameType = gameType
|
||||
}
|
||||
}
|
||||
|
||||
struct SustainInputsMapping: GameControllerInputMappingProtocol
|
||||
{
|
||||
let gameControllerInputType: GameControllerInputType
|
||||
let previousInputMapping: GameControllerInputMappingProtocol?
|
||||
|
||||
func input(forControllerInput controllerInput: Input) -> Input?
|
||||
{
|
||||
if let mappedInput = self.previousInputMapping?.input(forControllerInput: controllerInput), mappedInput == StandardGameControllerInput.menu
|
||||
{
|
||||
return mappedInput
|
||||
}
|
||||
|
||||
return controllerInput
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GameViewController: DeltaCore.GameViewController
|
||||
@ -79,10 +95,8 @@ class GameViewController: DeltaCore.GameViewController
|
||||
fileprivate var context = CIContext(options: [kCIContextWorkingColorSpace: NSNull()])
|
||||
|
||||
// Sustain Buttons
|
||||
fileprivate var updateSemaphores = Set<DispatchSemaphore>()
|
||||
fileprivate var sustainedInputs = [ObjectIdentifier: [Input]]()
|
||||
fileprivate var reactivateSustainedInputsQueue: OperationQueue
|
||||
fileprivate var selectingSustainedButtons = false
|
||||
fileprivate var isSelectingSustainedButtons = false
|
||||
fileprivate var sustainInputsMapping: SustainInputsMapping?
|
||||
|
||||
fileprivate var sustainButtonsContentView: UIView!
|
||||
fileprivate var sustainButtonsBlurView: UIVisualEffectView!
|
||||
@ -90,9 +104,6 @@ class GameViewController: DeltaCore.GameViewController
|
||||
|
||||
required init()
|
||||
{
|
||||
self.reactivateSustainedInputsQueue = OperationQueue()
|
||||
self.reactivateSustainedInputsQueue.maxConcurrentOperationCount = 1
|
||||
|
||||
super.init()
|
||||
|
||||
self.initialize()
|
||||
@ -100,9 +111,6 @@ class GameViewController: DeltaCore.GameViewController
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
self.reactivateSustainedInputsQueue = OperationQueue()
|
||||
self.reactivateSustainedInputsQueue.maxConcurrentOperationCount = 1
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.initialize()
|
||||
@ -112,8 +120,8 @@ class GameViewController: DeltaCore.GameViewController
|
||||
{
|
||||
self.delegate = self
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalControllerDidDisconnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalGameControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalGameControllerDidDisconnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.didEnterBackground(with:)), name: .UIApplicationDidEnterBackground, object: UIApplication.shared)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.settingsDidChange(with:)), name: .settingsDidChange, object: nil)
|
||||
}
|
||||
@ -128,18 +136,13 @@ class GameViewController: DeltaCore.GameViewController
|
||||
{
|
||||
super.gameController(gameController, didActivate: input)
|
||||
|
||||
guard (input as? ControllerInput) != .menu else { return }
|
||||
guard self.isSelectingSustainedButtons else { return }
|
||||
|
||||
if self.selectingSustainedButtons
|
||||
guard let pausingGameController = self.pausingGameController, gameController == pausingGameController else { return }
|
||||
|
||||
if input != StandardGameControllerInput.menu
|
||||
{
|
||||
self.addSustainedInput(input, for: gameController)
|
||||
}
|
||||
else if let sustainedInputs = self.sustainedInputs[ObjectIdentifier(gameController)], sustainedInputs.contains(where: { $0.isEqual(input) })
|
||||
{
|
||||
// Perform on next run loop
|
||||
DispatchQueue.main.async {
|
||||
self.reactivateSustainedInput(input, for: gameController)
|
||||
}
|
||||
gameController.sustain(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -243,22 +246,28 @@ extension GameViewController
|
||||
pauseViewController.saveStatesViewControllerDelegate = self
|
||||
pauseViewController.cheatsViewControllerDelegate = self
|
||||
|
||||
pauseViewController.fastForwardItem?.selected = (self.emulatorCore?.rate != self.emulatorCore?.deltaCore.supportedRates.lowerBound)
|
||||
pauseViewController.fastForwardItem?.isSelected = (self.emulatorCore?.rate != self.emulatorCore?.deltaCore.supportedRates.lowerBound)
|
||||
pauseViewController.fastForwardItem?.action = { [unowned self] item in
|
||||
guard let emulatorCore = self.emulatorCore else { return }
|
||||
emulatorCore.rate = item.selected ? emulatorCore.deltaCore.supportedRates.upperBound : emulatorCore.deltaCore.supportedRates.lowerBound
|
||||
emulatorCore.rate = item.isSelected ? emulatorCore.deltaCore.supportedRates.upperBound : emulatorCore.deltaCore.supportedRates.lowerBound
|
||||
}
|
||||
|
||||
pauseViewController.sustainButtonsItem?.selected = (self.sustainedInputs[ObjectIdentifier(gameController)]?.count ?? 0) > 0
|
||||
pauseViewController.sustainButtonsItem?.isSelected = gameController.sustainedInputs.count > 0
|
||||
pauseViewController.sustainButtonsItem?.action = { [unowned self, unowned pauseViewController] item in
|
||||
|
||||
self.resetSustainedInputs(for: gameController)
|
||||
for input in gameController.sustainedInputs
|
||||
{
|
||||
gameController.unsustain(input)
|
||||
}
|
||||
|
||||
if item.selected
|
||||
if item.isSelected
|
||||
{
|
||||
self.showSustainButtonView()
|
||||
pauseViewController.dismiss()
|
||||
}
|
||||
|
||||
// Re-set gameController as pausingGameController.
|
||||
self.pausingGameController = gameController
|
||||
}
|
||||
|
||||
self.pauseViewController = pauseViewController
|
||||
@ -346,11 +355,7 @@ private extension GameViewController
|
||||
{
|
||||
@objc func updateControllers()
|
||||
{
|
||||
var controllers = [GameController]()
|
||||
controllers.append(self.controllerView)
|
||||
|
||||
// We need to map each item as a GameControllerProtocol due to a Swift bug
|
||||
controllers.append(contentsOf: ExternalControllerManager.shared.connectedControllers.map { $0 as GameController })
|
||||
let controllers = [self.controllerView as GameController] + ExternalGameControllerManager.shared.connectedControllers
|
||||
|
||||
if let index = Settings.localControllerPlayerIndex
|
||||
{
|
||||
@ -590,17 +595,6 @@ extension GameViewController: SaveStatesViewControllerDelegate
|
||||
print(error)
|
||||
}
|
||||
|
||||
// Reactivate sustained inputs
|
||||
for gameController in self.emulatorCore?.gameControllers ?? []
|
||||
{
|
||||
guard let sustainedInputs = self.sustainedInputs[ObjectIdentifier(gameController)] else { continue }
|
||||
|
||||
for input in sustainedInputs
|
||||
{
|
||||
self.reactivateSustainedInput(input, for: gameController)
|
||||
}
|
||||
}
|
||||
|
||||
self.pauseViewController?.dismiss()
|
||||
}
|
||||
}
|
||||
@ -625,7 +619,12 @@ private extension GameViewController
|
||||
{
|
||||
func showSustainButtonView()
|
||||
{
|
||||
self.selectingSustainedButtons = true
|
||||
guard let gameController = self.pausingGameController else { return }
|
||||
|
||||
self.isSelectingSustainedButtons = true
|
||||
|
||||
self.sustainInputsMapping = SustainInputsMapping(gameControllerInputType: gameController.inputType, previousInputMapping: gameController.inputMapping)
|
||||
gameController.inputMapping = self.sustainInputsMapping
|
||||
|
||||
let blurEffect = self.sustainButtonsBlurView.effect
|
||||
self.sustainButtonsBlurView.effect = nil
|
||||
@ -640,7 +639,18 @@ private extension GameViewController
|
||||
|
||||
func hideSustainButtonView()
|
||||
{
|
||||
self.selectingSustainedButtons = false
|
||||
guard let gameController = self.pausingGameController else { return }
|
||||
|
||||
self.isSelectingSustainedButtons = false
|
||||
|
||||
gameController.inputMapping = self.sustainInputsMapping?.previousInputMapping
|
||||
self.sustainInputsMapping = nil
|
||||
|
||||
// Reactivate all sustained inputs, since they will now be mapped to game inputs.
|
||||
for input in gameController.sustainedInputs
|
||||
{
|
||||
gameController.activate(input)
|
||||
}
|
||||
|
||||
let blurEffect = self.sustainButtonsBlurView.effect
|
||||
|
||||
@ -652,96 +662,6 @@ private extension GameViewController
|
||||
self.sustainButtonsBlurView.effect = blurEffect
|
||||
}
|
||||
}
|
||||
|
||||
func resetSustainedInputs(for gameController: GameController)
|
||||
{
|
||||
if let previousInputs = self.sustainedInputs[ObjectIdentifier(gameController)]
|
||||
{
|
||||
let receivers = gameController.receivers
|
||||
receivers.forEach { gameController.removeReceiver($0) }
|
||||
|
||||
// Activate previousInputs without notifying anyone so we can then deactivate them
|
||||
// We do this because deactivating an already deactivated input has no effect
|
||||
previousInputs.forEach { gameController.activate($0) }
|
||||
|
||||
receivers.forEach { gameController.addReceiver($0) }
|
||||
|
||||
// Deactivate previously sustained inputs
|
||||
previousInputs.forEach { gameController.deactivate($0) }
|
||||
}
|
||||
|
||||
self.sustainedInputs[ObjectIdentifier(gameController)] = []
|
||||
}
|
||||
|
||||
func addSustainedInput(_ input: Input, for gameController: GameController)
|
||||
{
|
||||
var inputs = self.sustainedInputs[ObjectIdentifier(gameController)] ?? []
|
||||
|
||||
guard !inputs.contains(where: { $0.isEqual(input) }) else { return }
|
||||
|
||||
inputs.append(input)
|
||||
self.sustainedInputs[ObjectIdentifier(gameController)] = inputs
|
||||
|
||||
let receivers = gameController.receivers
|
||||
receivers.forEach { gameController.removeReceiver($0) }
|
||||
|
||||
// Causes input to be considered deactivated, so gameController won't send a subsequent message to observers when user actually deactivates
|
||||
// However, at this point the core still thinks it is activated, and is temporarily not a receiver, thus sustaining it
|
||||
gameController.deactivate(input)
|
||||
|
||||
receivers.forEach { gameController.addReceiver($0) }
|
||||
}
|
||||
|
||||
func reactivateSustainedInput(_ input: Input, for gameController: GameController)
|
||||
{
|
||||
// These MUST be performed serially, or else Bad Things Happen™ if multiple inputs are reactivated at once
|
||||
self.reactivateSustainedInputsQueue.addOperation {
|
||||
|
||||
// The manual activations/deactivations here are hidden implementation details, so we won't notify ourselves about them
|
||||
gameController.removeReceiver(self)
|
||||
|
||||
// Must deactivate first so core recognizes a secondary activation
|
||||
gameController.deactivate(input)
|
||||
|
||||
let dispatchQueue = DispatchQueue(label: "com.rileytestut.Delta.sustainButtonsQueue")
|
||||
dispatchQueue.async {
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
self.updateSemaphores.insert(semaphore)
|
||||
|
||||
// To ensure the emulator core recognizes us activating the input again, we need to wait at least two frames
|
||||
// Unfortunately we cannot init DispatchSemaphore with value less than 0
|
||||
// To compensate, we simply wait twice; once the first wait returns, we wait again
|
||||
semaphore.wait()
|
||||
semaphore.wait()
|
||||
|
||||
// These MUST be performed serially, or else Bad Things Happen™ if multiple inputs are reactivated at once
|
||||
self.reactivateSustainedInputsQueue.addOperation {
|
||||
|
||||
self.updateSemaphores.remove(semaphore)
|
||||
|
||||
// Ensure we still are not a receiver (to prevent rare race conditions)
|
||||
gameController.removeReceiver(self)
|
||||
|
||||
gameController.activate(input)
|
||||
|
||||
let receivers = gameController.receivers
|
||||
receivers.forEach { gameController.removeReceiver($0) }
|
||||
|
||||
// Causes input to be considered deactivated, so gameController won't send a subsequent message to observers when user actually deactivates
|
||||
// However, at this point the core still thinks it is activated, and is temporarily not a receiver, thus sustaining it
|
||||
gameController.deactivate(input)
|
||||
|
||||
receivers.forEach { gameController.addReceiver($0) }
|
||||
}
|
||||
|
||||
// More Bad Things Happen™ if we add self as observer before ALL reactivations have occurred (notable, infinite loops)
|
||||
self.reactivateSustainedInputsQueue.waitUntilAllOperationsAreFinished()
|
||||
|
||||
gameController.addReceiver(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - GameViewControllerDelegate -
|
||||
@ -750,12 +670,17 @@ extension GameViewController: GameViewControllerDelegate
|
||||
{
|
||||
func gameViewController(_ gameViewController: DeltaCore.GameViewController, handleMenuInputFrom gameController: GameController)
|
||||
{
|
||||
if self.selectingSustainedButtons
|
||||
if let pausingGameController = self.pausingGameController
|
||||
{
|
||||
guard pausingGameController == gameController else { return }
|
||||
}
|
||||
|
||||
if self.isSelectingSustainedButtons
|
||||
{
|
||||
self.hideSustainButtonView()
|
||||
}
|
||||
|
||||
if let pauseViewController = self.pauseViewController, !self.selectingSustainedButtons
|
||||
if let pauseViewController = self.pauseViewController, !self.isSelectingSustainedButtons
|
||||
{
|
||||
pauseViewController.dismiss()
|
||||
}
|
||||
@ -768,15 +693,7 @@ extension GameViewController: GameViewControllerDelegate
|
||||
|
||||
func gameViewControllerShouldResumeEmulation(_ gameViewController: DeltaCore.GameViewController) -> Bool
|
||||
{
|
||||
return (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.selectingSustainedButtons && self.view.window != nil
|
||||
}
|
||||
|
||||
func gameViewControllerDidUpdate(_ gameViewController: DeltaCore.GameViewController)
|
||||
{
|
||||
for semaphore in self.updateSemaphores
|
||||
{
|
||||
semaphore.signal()
|
||||
}
|
||||
return (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.isSelectingSustainedButtons && self.view.window != nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
112
Delta/Extensions/Input+Display.swift
Normal file
112
Delta/Extensions/Input+Display.swift
Normal file
@ -0,0 +1,112 @@
|
||||
//
|
||||
// Input+Display.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 8/15/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import DeltaCore
|
||||
|
||||
extension Input
|
||||
{
|
||||
// With the default GameControllerInputMapping files, multiple controller inputs may map to the same game input.
|
||||
// This is because each controller input maps to a unique standard input, but then multiple standard inputs may map to same game input.
|
||||
// To ensure we only show the most "important" controller input for a game input, we define general "display priorities" for each input.
|
||||
//
|
||||
// For example, MFiGameController.down and MFiGameController.leftThumbstickDown both map to a "down" game input.
|
||||
// However, .down has a higher priority than .leftThumbstickDown, so we show .down instead of .leftThumbstickDown.
|
||||
var displayPriority: Int {
|
||||
switch self.type
|
||||
{
|
||||
case .game: break
|
||||
case .controller(.standard): break
|
||||
case .controller(.mfi):
|
||||
let input = MFiGameController.Input(input: self)!
|
||||
switch input
|
||||
{
|
||||
case .leftThumbstickUp: return 750
|
||||
case .leftThumbstickDown: return 750
|
||||
case .leftThumbstickLeft: return 750
|
||||
case .leftThumbstickRight: return 750
|
||||
case .leftShoulder: return 750
|
||||
case .leftTrigger: return 500
|
||||
case .rightShoulder: return 750
|
||||
case .rightTrigger: return 500
|
||||
default: break
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
|
||||
return 1000
|
||||
}
|
||||
|
||||
var localizedName: String {
|
||||
switch self.type
|
||||
{
|
||||
case .game: break
|
||||
case .controller(.standard):
|
||||
let input = StandardGameControllerInput(input: self)!
|
||||
switch input
|
||||
{
|
||||
case .menu: return NSLocalizedString("Menu", comment: "")
|
||||
case .up: return NSLocalizedString("Up", comment: "")
|
||||
case .down: return NSLocalizedString("Down", comment: "")
|
||||
case .left: return NSLocalizedString("Left", comment: "")
|
||||
case .right: return NSLocalizedString("Right", comment: "")
|
||||
case .leftThumbstickUp: return NSLocalizedString("L🕹↑", comment: "")
|
||||
case .leftThumbstickDown: return NSLocalizedString("L🕹↓", comment: "")
|
||||
case .leftThumbstickLeft: return NSLocalizedString("L🕹←", comment: "")
|
||||
case .leftThumbstickRight: return NSLocalizedString("L🕹→", comment: "")
|
||||
case .rightThumbstickUp: return NSLocalizedString("R🕹↑", comment: "")
|
||||
case .rightThumbstickDown: return NSLocalizedString("R🕹↓", comment: "")
|
||||
case .rightThumbstickLeft: return NSLocalizedString("R🕹←", comment: "")
|
||||
case .rightThumbstickRight: return NSLocalizedString("R🕹→", comment: "")
|
||||
case .a: return NSLocalizedString("A", comment: "")
|
||||
case .b: return NSLocalizedString("B", comment: "")
|
||||
case .x: return NSLocalizedString("X", comment: "")
|
||||
case .y: return NSLocalizedString("Y", comment: "")
|
||||
case .start: return NSLocalizedString("Start", comment: "Start button")
|
||||
case .select: return NSLocalizedString("Select", comment: "Select button")
|
||||
case .l1: return NSLocalizedString("L1", comment: "")
|
||||
case .l2: return NSLocalizedString("L2", comment: "")
|
||||
case .l3: return NSLocalizedString("L3", comment: "")
|
||||
case .r1: return NSLocalizedString("R1", comment: "")
|
||||
case .r2: return NSLocalizedString("R2", comment: "")
|
||||
case .r3: return NSLocalizedString("R3", comment: "")
|
||||
}
|
||||
|
||||
case .controller(.mfi):
|
||||
let input = MFiGameController.Input(input: self)!
|
||||
switch input
|
||||
{
|
||||
case .menu: return NSLocalizedString("Menu", comment: "")
|
||||
case .up: return NSLocalizedString("Up", comment: "")
|
||||
case .down: return NSLocalizedString("Down", comment: "")
|
||||
case .left: return NSLocalizedString("Left", comment: "")
|
||||
case .right: return NSLocalizedString("Right", comment: "")
|
||||
case .leftThumbstickUp: return NSLocalizedString("L🕹↑", comment: "")
|
||||
case .leftThumbstickDown: return NSLocalizedString("L🕹↓", comment: "")
|
||||
case .leftThumbstickLeft: return NSLocalizedString("L🕹←", comment: "")
|
||||
case .leftThumbstickRight: return NSLocalizedString("L🕹→", comment: "")
|
||||
case .rightThumbstickUp: return NSLocalizedString("R🕹↑", comment: "")
|
||||
case .rightThumbstickDown: return NSLocalizedString("R🕹↓", comment: "")
|
||||
case .rightThumbstickLeft: return NSLocalizedString("R🕹←", comment: "")
|
||||
case .rightThumbstickRight: return NSLocalizedString("R🕹→", comment: "")
|
||||
case .a: return NSLocalizedString("A", comment: "")
|
||||
case .b: return NSLocalizedString("B", comment: "")
|
||||
case .x: return NSLocalizedString("X", comment: "")
|
||||
case .y: return NSLocalizedString("Y", comment: "")
|
||||
case .leftShoulder: return NSLocalizedString("L1", comment: "")
|
||||
case .leftTrigger: return NSLocalizedString("L2", comment: "")
|
||||
case .rightShoulder: return NSLocalizedString("R1", comment: "")
|
||||
case .rightTrigger: return NSLocalizedString("R2", comment: "")
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
}
|
||||
28
Delta/Extensions/UIView+ParentViewController.swift
Normal file
28
Delta/Extensions/UIView+ParentViewController.swift
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// UIView+ParentViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/3/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView
|
||||
{
|
||||
var parentViewController: UIViewController? {
|
||||
var nextResponder = self.next
|
||||
|
||||
while nextResponder != nil
|
||||
{
|
||||
if let parentViewController = nextResponder as? UIViewController
|
||||
{
|
||||
return parentViewController
|
||||
}
|
||||
|
||||
nextResponder = nextResponder?.next
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// PauseMenuViewController.swift
|
||||
// GridMenuViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 12/21/15.
|
||||
@ -9,39 +9,57 @@
|
||||
import UIKit
|
||||
import Roxas
|
||||
|
||||
class PauseMenuViewController: UICollectionViewController
|
||||
class GridMenuViewController: UICollectionViewController
|
||||
{
|
||||
var items = [PauseItem]() {
|
||||
didSet
|
||||
{
|
||||
guard oldValue != self.items else { return }
|
||||
|
||||
if self.items.count > 8
|
||||
{
|
||||
fatalError("PauseViewController only supports up to 8 items (for my sanity when laying out on a landscape iPhone 4s")
|
||||
var items: [MenuItem] {
|
||||
get { return self.dataSource.items }
|
||||
set { self.dataSource.items = newValue; self.updateItems() }
|
||||
}
|
||||
|
||||
self.dataSource.items = self.items
|
||||
}
|
||||
}
|
||||
var isVibrancyEnabled = true
|
||||
|
||||
override var preferredContentSize: CGSize {
|
||||
set { }
|
||||
get { return self.collectionView?.contentSize ?? CGSize.zero }
|
||||
}
|
||||
|
||||
fileprivate let dataSource = RSTArrayCollectionViewDataSource<PauseItem>(items: [])
|
||||
fileprivate let dataSource = RSTArrayCollectionViewDataSource<MenuItem>(items: [])
|
||||
|
||||
fileprivate var prototypeCell = GridCollectionViewCell()
|
||||
fileprivate var previousIndexPath: IndexPath? = nil
|
||||
|
||||
fileprivate var registeredKVOObservers = Set<NSKeyValueObservation>()
|
||||
|
||||
init()
|
||||
{
|
||||
let collectionViewLayout = GridCollectionViewLayout()
|
||||
collectionViewLayout.itemSize = CGSize(width: 60, height: 80)
|
||||
collectionViewLayout.minimumLineSpacing = 20
|
||||
collectionViewLayout.minimumInteritemSpacing = 10
|
||||
|
||||
super.init(collectionViewLayout: collectionViewLayout)
|
||||
}
|
||||
|
||||
extension PauseMenuViewController
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
deinit
|
||||
{
|
||||
// Crashes on iOS 10 if not explicitly invalidated.
|
||||
self.registeredKVOObservers.forEach { $0.invalidate() }
|
||||
}
|
||||
}
|
||||
|
||||
extension GridMenuViewController
|
||||
{
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.collectionView?.register(GridCollectionViewCell.self, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier)
|
||||
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
|
||||
self.configure(cell as! GridCollectionViewCell, for: indexPath)
|
||||
}
|
||||
@ -62,55 +80,66 @@ extension PauseMenuViewController
|
||||
if let indexPath = self.previousIndexPath
|
||||
{
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.toggleSelectedStateForPauseItemAtIndexPath(indexPath)
|
||||
let item = self.items[indexPath.item]
|
||||
item.isSelected = !item.isSelected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension PauseMenuViewController
|
||||
private extension GridMenuViewController
|
||||
{
|
||||
func configure(_ cell: GridCollectionViewCell, for indexPath: IndexPath)
|
||||
{
|
||||
let pauseItem = self.items[(indexPath as NSIndexPath).item]
|
||||
let pauseItem = self.items[indexPath.item]
|
||||
|
||||
cell.maximumImageSize = CGSize(width: 60, height: 60)
|
||||
|
||||
cell.imageView.image = pauseItem.image
|
||||
cell.imageView.contentMode = .center
|
||||
cell.imageView.layer.borderWidth = 2
|
||||
cell.imageView.layer.borderColor = UIColor.white.cgColor
|
||||
cell.imageView.layer.borderColor = self.view.tintColor.cgColor
|
||||
cell.imageView.layer.cornerRadius = 10
|
||||
|
||||
cell.textLabel.text = pauseItem.text
|
||||
cell.textLabel.textColor = UIColor.white
|
||||
cell.textLabel.textColor = self.view.tintColor
|
||||
|
||||
if pauseItem.selected
|
||||
if pauseItem.isSelected
|
||||
{
|
||||
cell.imageView.tintColor = UIColor.black
|
||||
cell.imageView.backgroundColor = UIColor.white
|
||||
cell.imageView.backgroundColor = self.view.tintColor
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.imageView.tintColor = UIColor.white
|
||||
cell.imageView.tintColor = self.view.tintColor
|
||||
cell.imageView.backgroundColor = UIColor.clear
|
||||
}
|
||||
|
||||
cell.isImageViewVibrancyEnabled = true
|
||||
cell.isTextLabelVibrancyEnabled = true
|
||||
cell.isImageViewVibrancyEnabled = self.isVibrancyEnabled
|
||||
cell.isTextLabelVibrancyEnabled = self.isVibrancyEnabled
|
||||
}
|
||||
|
||||
func toggleSelectedStateForPauseItemAtIndexPath(_ indexPath: IndexPath)
|
||||
func updateItems()
|
||||
{
|
||||
let pauseItem = self.items[indexPath.item]
|
||||
pauseItem.selected = !pauseItem.selected
|
||||
self.registeredKVOObservers.removeAll()
|
||||
|
||||
let cell = self.collectionView!.cellForItem(at: indexPath) as! GridCollectionViewCell
|
||||
for (index, item) in self.items.enumerated()
|
||||
{
|
||||
let observer = item.observe(\.isSelected, changeHandler: { [unowned self] (item, change) in
|
||||
let indexPath = IndexPath(item: index, section: 0)
|
||||
|
||||
if let cell = self.collectionView?.cellForItem(at: indexPath) as? GridCollectionViewCell
|
||||
{
|
||||
self.configure(cell, for: indexPath)
|
||||
}
|
||||
})
|
||||
|
||||
self.registeredKVOObservers.insert(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PauseMenuViewController: UICollectionViewDelegateFlowLayout
|
||||
extension GridMenuViewController: UICollectionViewDelegateFlowLayout
|
||||
{
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
|
||||
{
|
||||
@ -121,26 +150,27 @@ extension PauseMenuViewController: UICollectionViewDelegateFlowLayout
|
||||
}
|
||||
}
|
||||
|
||||
extension PauseMenuViewController
|
||||
extension GridMenuViewController
|
||||
{
|
||||
override func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath)
|
||||
{
|
||||
self.toggleSelectedStateForPauseItemAtIndexPath(indexPath)
|
||||
let item = self.items[indexPath.item]
|
||||
item.isSelected = !item.isSelected
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath)
|
||||
{
|
||||
self.toggleSelectedStateForPauseItemAtIndexPath(indexPath)
|
||||
let item = self.items[indexPath.item]
|
||||
item.isSelected = !item.isSelected
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||
{
|
||||
self.previousIndexPath = indexPath
|
||||
|
||||
self.toggleSelectedStateForPauseItemAtIndexPath(indexPath)
|
||||
|
||||
let pauseItem = self.items[indexPath.item]
|
||||
pauseItem.action(pauseItem)
|
||||
let item = self.items[indexPath.item]
|
||||
item.isSelected = !item.isSelected
|
||||
item.action(item)
|
||||
}
|
||||
}
|
||||
|
||||
35
Delta/Pause Menu/MenuItem.swift
Normal file
35
Delta/Pause Menu/MenuItem.swift
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// MenuItem.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 1/30/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// Must be class for use with Objective-C generics :(
|
||||
class MenuItem: NSObject
|
||||
{
|
||||
var text: String
|
||||
var image: UIImage?
|
||||
var action: ((MenuItem) -> Void)
|
||||
|
||||
@objc dynamic var isSelected = false
|
||||
|
||||
init(text: String, image: UIImage?, action: @escaping ((MenuItem) -> Void))
|
||||
{
|
||||
self.image = image
|
||||
self.text = text
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
extension MenuItem
|
||||
{
|
||||
override func isEqual(_ object: Any?) -> Bool
|
||||
{
|
||||
guard let item = object as? MenuItem else { return false }
|
||||
return item.image == self.image && item.text == self.text
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
//
|
||||
// PauseItem.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 1/30/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// Must be class for use with Objective-C generics :(
|
||||
class PauseItem: Equatable
|
||||
{
|
||||
var image: UIImage
|
||||
var text: String
|
||||
var action: ((PauseItem) -> Void)
|
||||
|
||||
var selected = false
|
||||
|
||||
init(image: UIImage, text: String, action: @escaping ((PauseItem) -> Void))
|
||||
{
|
||||
self.image = image
|
||||
self.text = text
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
func ==(lhs: PauseItem, rhs: PauseItem) -> Bool
|
||||
{
|
||||
return (lhs.image == rhs.image) && (lhs.text == rhs.text)
|
||||
}
|
||||
@ -18,16 +18,16 @@ class PauseViewController: UIViewController, PauseInfoProviding
|
||||
}
|
||||
}
|
||||
|
||||
var pauseItems: [PauseItem] {
|
||||
var pauseItems: [MenuItem] {
|
||||
return [self.saveStateItem, self.loadStateItem, self.cheatCodesItem, self.fastForwardItem, self.sustainButtonsItem].flatMap { $0 }
|
||||
}
|
||||
|
||||
/// Pause Items
|
||||
var saveStateItem: PauseItem?
|
||||
var loadStateItem: PauseItem?
|
||||
var cheatCodesItem: PauseItem?
|
||||
var fastForwardItem: PauseItem?
|
||||
var sustainButtonsItem: PauseItem?
|
||||
var saveStateItem: MenuItem?
|
||||
var loadStateItem: MenuItem?
|
||||
var cheatCodesItem: MenuItem?
|
||||
var fastForwardItem: MenuItem?
|
||||
var sustainButtonsItem: MenuItem?
|
||||
|
||||
/// PauseInfoProviding
|
||||
var pauseText: String?
|
||||
@ -85,8 +85,8 @@ extension PauseViewController
|
||||
self.pauseNavigationController.navigationBar.tintColor = UIColor.deltaPurple
|
||||
self.pauseNavigationController.view.backgroundColor = UIColor.clear
|
||||
|
||||
let pauseMenuViewController = self.pauseNavigationController.topViewController as! PauseMenuViewController
|
||||
pauseMenuViewController.items = self.pauseItems
|
||||
let gridMenuViewController = self.pauseNavigationController.topViewController as! GridMenuViewController
|
||||
gridMenuViewController.items = self.pauseItems
|
||||
|
||||
case "saveStates":
|
||||
let saveStatesViewController = segue.destination as! SaveStatesViewController
|
||||
@ -135,21 +135,21 @@ private extension PauseViewController
|
||||
|
||||
guard self.emulatorCore != nil else { return }
|
||||
|
||||
self.saveStateItem = PauseItem(image: #imageLiteral(resourceName: "SaveSaveState"), text: NSLocalizedString("Save State", comment: ""), action: { [unowned self] _ in
|
||||
self.saveStateItem = MenuItem(text: NSLocalizedString("Save State", comment: ""), image: #imageLiteral(resourceName: "SaveSaveState"), action: { [unowned self] _ in
|
||||
self.saveStatesViewControllerMode = .saving
|
||||
self.performSegue(withIdentifier: "saveStates", sender: self)
|
||||
})
|
||||
|
||||
self.loadStateItem = PauseItem(image: #imageLiteral(resourceName: "LoadSaveState"), text: NSLocalizedString("Load State", comment: ""), action: { [unowned self] _ in
|
||||
self.loadStateItem = MenuItem(text: NSLocalizedString("Load State", comment: ""), image: #imageLiteral(resourceName: "LoadSaveState"), action: { [unowned self] _ in
|
||||
self.saveStatesViewControllerMode = .loading
|
||||
self.performSegue(withIdentifier: "saveStates", sender: self)
|
||||
})
|
||||
|
||||
self.cheatCodesItem = PauseItem(image: #imageLiteral(resourceName: "CheatCodes"), text: NSLocalizedString("Cheat Codes", comment: ""), action: { [unowned self] _ in
|
||||
self.cheatCodesItem = MenuItem(text: NSLocalizedString("Cheat Codes", comment: ""), image: #imageLiteral(resourceName: "CheatCodes"), action: { [unowned self] _ in
|
||||
self.performSegue(withIdentifier: "cheats", sender: self)
|
||||
})
|
||||
|
||||
self.fastForwardItem = PauseItem(image: #imageLiteral(resourceName: "FastForward"), text: NSLocalizedString("Fast Forward", comment: ""), action: { _ in })
|
||||
self.sustainButtonsItem = PauseItem(image: #imageLiteral(resourceName: "SustainButtons"), text: NSLocalizedString("Sustain Buttons", comment: ""), action: { _ in })
|
||||
self.fastForwardItem = MenuItem(text: NSLocalizedString("Fast Forward", comment: ""), image: #imageLiteral(resourceName: "FastForward"), action: { _ in })
|
||||
self.sustainButtonsItem = MenuItem(text: NSLocalizedString("Sustain Buttons", comment: ""), image: #imageLiteral(resourceName: "SustainButtons"), action: { _ in })
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,9 @@ class PauseStoryboardSegue: UIStoryboardSegue
|
||||
self.destination.modalPresentationStyle = .custom
|
||||
self.destination.modalPresentationCapturesStatusBarAppearance = true
|
||||
|
||||
// Manually set tint color, since calling layoutIfNeeded will cause view to load, but with default system tint color.
|
||||
self.destination.view.tintColor = .white
|
||||
|
||||
// We need to force layout of destinationViewController.view _before_ animateTransition(using:)
|
||||
// Otherwise, we'll get "Unable to simultaneously satisfy constraints" errors
|
||||
self.destination.view.frame = self.source.view.frame
|
||||
|
||||
480
Delta/Settings/Controllers/ControllerInputsViewController.swift
Normal file
480
Delta/Settings/Controllers/ControllerInputsViewController.swift
Normal file
@ -0,0 +1,480 @@
|
||||
//
|
||||
// ControllerInputsViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 7/1/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Roxas
|
||||
|
||||
import DeltaCore
|
||||
|
||||
import SMCalloutView
|
||||
|
||||
class ControllerInputsViewController: UIViewController
|
||||
{
|
||||
var gameController: GameController! {
|
||||
didSet {
|
||||
self.prepareGameController()
|
||||
}
|
||||
}
|
||||
|
||||
var system: System = .snes {
|
||||
didSet {
|
||||
guard self.system != oldValue else { return }
|
||||
self.updateSystem()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var inputMapping: GameControllerInputMapping!
|
||||
fileprivate var previousInputMapping: GameControllerInputMappingProtocol?
|
||||
|
||||
fileprivate let supportedActionInputs: [ActionInput] = [.saveState, .loadState, .fastForward]
|
||||
|
||||
fileprivate var gameViewController: DeltaCore.GameViewController!
|
||||
fileprivate var actionsMenuViewController: GridMenuViewController!
|
||||
|
||||
fileprivate var calloutViews = [AnyInput: InputCalloutView]()
|
||||
|
||||
fileprivate var activeCalloutView: InputCalloutView?
|
||||
|
||||
@IBOutlet private var actionsMenuViewControllerHeightConstraint: NSLayoutConstraint!
|
||||
@IBOutlet private var cancelTapGestureRecognizer: UITapGestureRecognizer!
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.gameViewController.controllerView.addReceiver(self)
|
||||
|
||||
self.navigationController?.navigationBar.barStyle = .black
|
||||
|
||||
NSLayoutConstraint.activate([self.gameViewController.gameView.centerYAnchor.constraint(equalTo: self.actionsMenuViewController.view.centerYAnchor)])
|
||||
|
||||
self.preparePopoverMenuController()
|
||||
self.updateSystem()
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews()
|
||||
{
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
if self.actionsMenuViewController.preferredContentSize.height > 0
|
||||
{
|
||||
self.actionsMenuViewControllerHeightConstraint.constant = self.actionsMenuViewController.preferredContentSize.height
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool)
|
||||
{
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if self.calloutViews.isEmpty
|
||||
{
|
||||
self.prepareCallouts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerInputsViewController
|
||||
{
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||
{
|
||||
guard let identifier = segue.identifier else { return }
|
||||
|
||||
switch identifier
|
||||
{
|
||||
case "embedGameViewController": self.gameViewController = segue.destination as! DeltaCore.GameViewController
|
||||
case "embedActionsMenuViewController":
|
||||
self.actionsMenuViewController = segue.destination as! GridMenuViewController
|
||||
self.prepareActionsMenuViewController()
|
||||
|
||||
case "cancelControllerControls": self.gameController.inputMapping = self.previousInputMapping
|
||||
case "saveControllerControls": self.gameController.inputMapping = self.inputMapping
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllerInputsViewController
|
||||
{
|
||||
func updateSystem()
|
||||
{
|
||||
guard self.isViewLoaded else { return }
|
||||
|
||||
if let popoverMenuButton = self.navigationItem.popoverMenuController?.popoverMenuButton
|
||||
{
|
||||
popoverMenuButton.title = self.system.localizedShortName
|
||||
popoverMenuButton.bounds.size = popoverMenuButton.intrinsicContentSize
|
||||
|
||||
self.navigationController?.navigationBar.layoutIfNeeded()
|
||||
}
|
||||
|
||||
self.gameViewController.controllerView.controllerSkin = DeltaCore.ControllerSkin.standardControllerSkin(for: self.system.gameType)
|
||||
|
||||
if self.view.window != nil
|
||||
{
|
||||
self.calloutViews.forEach { $1.dismissCallout(animated: true) }
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
self.calloutViews = [:]
|
||||
self.prepareCallouts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func prepareGameController()
|
||||
{
|
||||
self.gameController.addReceiver(self)
|
||||
|
||||
self.previousInputMapping = self.gameController.inputMapping
|
||||
|
||||
self.inputMapping = self.gameController.inputMapping as? GameControllerInputMapping ?? GameControllerInputMapping(gameControllerInputType: self.gameController.inputType)
|
||||
self.inputMapping.name = String.localizedStringWithFormat("Custom %@", self.gameController.name)
|
||||
|
||||
self.gameController.inputMapping = nil
|
||||
}
|
||||
|
||||
func preparePopoverMenuController()
|
||||
{
|
||||
let listMenuViewController = ListMenuViewController()
|
||||
listMenuViewController.title = NSLocalizedString("Game System", comment: "")
|
||||
|
||||
let navigationController = UINavigationController(rootViewController: listMenuViewController)
|
||||
|
||||
let popoverMenuController = PopoverMenuController(popoverViewController: navigationController)
|
||||
self.navigationItem.popoverMenuController = popoverMenuController
|
||||
|
||||
let items = System.supportedSystems.map { [unowned self, weak popoverMenuController, weak listMenuViewController] system -> MenuItem in
|
||||
let item = MenuItem(text: system.localizedShortName, image: #imageLiteral(resourceName: "CheatCodes")) { [weak popoverMenuController, weak listMenuViewController] item in
|
||||
listMenuViewController?.items.forEach { $0.isSelected = ($0 == item) }
|
||||
popoverMenuController?.isActive = false
|
||||
|
||||
self.system = system
|
||||
}
|
||||
item.isSelected = (system == self.system)
|
||||
|
||||
return item
|
||||
}
|
||||
listMenuViewController.items = items
|
||||
}
|
||||
|
||||
func prepareActionsMenuViewController()
|
||||
{
|
||||
var items = [MenuItem]()
|
||||
|
||||
for input in self.supportedActionInputs
|
||||
{
|
||||
let image: UIImage
|
||||
let text: String
|
||||
|
||||
switch input
|
||||
{
|
||||
case .saveState:
|
||||
image = #imageLiteral(resourceName: "SaveSaveState")
|
||||
text = NSLocalizedString("Save State", comment: "")
|
||||
|
||||
case .loadState:
|
||||
image = #imageLiteral(resourceName: "LoadSaveState")
|
||||
text = NSLocalizedString("Load State", comment: "")
|
||||
|
||||
case .fastForward:
|
||||
image = #imageLiteral(resourceName: "FastForward")
|
||||
text = NSLocalizedString("Fast Forward", comment: "")
|
||||
}
|
||||
|
||||
let item = MenuItem(text: text, image: image) { (item) in
|
||||
guard let calloutView = self.calloutViews[AnyInput(input)] else { return }
|
||||
self.toggle(calloutView)
|
||||
}
|
||||
|
||||
items.append(item)
|
||||
}
|
||||
|
||||
self.actionsMenuViewController.items = items
|
||||
self.actionsMenuViewController.isVibrancyEnabled = false
|
||||
}
|
||||
|
||||
func prepareCallouts()
|
||||
{
|
||||
guard
|
||||
let controllerView = self.gameViewController.controllerView,
|
||||
let traits = controllerView.controllerSkinTraits,
|
||||
let items = controllerView.controllerSkin?.items(for: traits),
|
||||
let controllerViewInputMapping = controllerView.inputMapping
|
||||
else { return }
|
||||
|
||||
// Implicit assumption that all skins used for controller input mapping don't have multiple items with same input.
|
||||
let mappedInputs = items.flatMap { $0.inputs.allInputs.flatMap(controllerViewInputMapping.input(forControllerInput:)) } + (self.supportedActionInputs as [Input])
|
||||
|
||||
// Create callout view for each on-screen input.
|
||||
for input in mappedInputs
|
||||
{
|
||||
let calloutView = InputCalloutView()
|
||||
calloutView.delegate = self
|
||||
self.calloutViews[AnyInput(input)] = calloutView
|
||||
}
|
||||
|
||||
// Update callout views with controller inputs that map to callout views' associated controller skin inputs.
|
||||
for input in self.inputMapping.supportedControllerInputs
|
||||
{
|
||||
let mappedInput = self.mappedInput(for: input)
|
||||
|
||||
if let calloutView = self.calloutViews[mappedInput]
|
||||
{
|
||||
if let previousInput = calloutView.input
|
||||
{
|
||||
// Ensure the input we display has a higher priority.
|
||||
calloutView.input = (input.displayPriority > previousInput.displayPriority) ? input : previousInput
|
||||
}
|
||||
else
|
||||
{
|
||||
calloutView.input = input
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Present only callout views that are associated with a controller input.
|
||||
for calloutView in self.calloutViews.values
|
||||
{
|
||||
if let presentationRect = self.presentationRect(for: calloutView), calloutView.input != nil
|
||||
{
|
||||
calloutView.presentCallout(from: presentationRect, in: self.view, constrainedTo: self.view, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllerInputsViewController
|
||||
{
|
||||
func updateActiveCalloutView(with controllerInput: Input?)
|
||||
{
|
||||
guard let activeCalloutView = self.activeCalloutView else { return }
|
||||
|
||||
guard let input = self.calloutViews.first(where: { $0.value == activeCalloutView })?.key else { return }
|
||||
|
||||
if let controllerInput = controllerInput
|
||||
{
|
||||
for (_, calloutView) in self.calloutViews
|
||||
{
|
||||
guard let calloutInput = calloutView.input else { continue }
|
||||
|
||||
if calloutInput == controllerInput
|
||||
{
|
||||
// Hide callout views that previously displayed the controller input.
|
||||
calloutView.input = nil
|
||||
calloutView.dismissCallout(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for supportedInput in self.inputMapping.supportedControllerInputs
|
||||
{
|
||||
let mappedInput = self.mappedInput(for: supportedInput)
|
||||
|
||||
if mappedInput == input
|
||||
{
|
||||
// Set all existing controller inputs that currently map to "input" to instead map to nil.
|
||||
self.inputMapping.set(nil, forControllerInput: supportedInput)
|
||||
}
|
||||
}
|
||||
|
||||
if let controllerInput = controllerInput
|
||||
{
|
||||
self.inputMapping.set(input, forControllerInput: controllerInput)
|
||||
}
|
||||
|
||||
activeCalloutView.input = controllerInput
|
||||
|
||||
self.toggle(activeCalloutView)
|
||||
}
|
||||
|
||||
func toggle(_ calloutView: InputCalloutView)
|
||||
{
|
||||
if let activeCalloutView = self.activeCalloutView, activeCalloutView != calloutView
|
||||
{
|
||||
self.toggle(activeCalloutView)
|
||||
}
|
||||
|
||||
let menuItem: MenuItem?
|
||||
|
||||
if let input = self.calloutViews.first(where: { $0.value == calloutView })?.key, let index = self.supportedActionInputs.index(where: { $0 == input })
|
||||
{
|
||||
menuItem = self.actionsMenuViewController.items[index]
|
||||
}
|
||||
else
|
||||
{
|
||||
menuItem = nil
|
||||
}
|
||||
|
||||
switch calloutView.state
|
||||
{
|
||||
case .normal:
|
||||
calloutView.state = .listening
|
||||
menuItem?.isSelected = true
|
||||
self.activeCalloutView = calloutView
|
||||
|
||||
case .listening:
|
||||
calloutView.state = .normal
|
||||
menuItem?.isSelected = false
|
||||
self.activeCalloutView = nil
|
||||
}
|
||||
|
||||
calloutView.dismissCallout(animated: true)
|
||||
|
||||
if let presentationRect = self.presentationRect(for: calloutView)
|
||||
{
|
||||
if calloutView.state == .listening || calloutView.input != nil
|
||||
{
|
||||
calloutView.presentCallout(from: presentationRect, in: self.view, constrainedTo: self.view, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerInputsViewController: UIGestureRecognizerDelegate
|
||||
{
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
|
||||
{
|
||||
return self.activeCalloutView != nil
|
||||
}
|
||||
|
||||
@IBAction private func handleTapGesture(_ tapGestureRecognizer: UITapGestureRecognizer)
|
||||
{
|
||||
self.updateActiveCalloutView(with: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllerInputsViewController
|
||||
{
|
||||
func mappedInput(for input: Input) -> AnyInput
|
||||
{
|
||||
guard let mappedInput = self.inputMapping.input(forControllerInput: input) else {
|
||||
fatalError("Mapped input for provided input does not exist.")
|
||||
}
|
||||
|
||||
if let standardInput = StandardGameControllerInput(input: mappedInput)
|
||||
{
|
||||
if let gameInput = standardInput.input(for: self.system.gameType)
|
||||
{
|
||||
return AnyInput(gameInput)
|
||||
}
|
||||
}
|
||||
|
||||
return AnyInput(mappedInput)
|
||||
}
|
||||
|
||||
func presentationRect(for calloutView: InputCalloutView) -> CGRect?
|
||||
{
|
||||
guard let input = self.calloutViews.first(where: { $0.value == calloutView })?.key else { return nil }
|
||||
|
||||
guard
|
||||
let controllerView = self.gameViewController.controllerView,
|
||||
let traits = controllerView.controllerSkinTraits,
|
||||
let items = controllerView.controllerSkin?.items(for: traits)
|
||||
else { return nil }
|
||||
|
||||
if let item = items.first(where: { $0.inputs.allInputs.contains(where: { $0.stringValue == input.stringValue })})
|
||||
{
|
||||
// Input is a controller skin input.
|
||||
|
||||
let itemFrame: CGRect?
|
||||
|
||||
switch item.inputs
|
||||
{
|
||||
case .standard: itemFrame = item.frame
|
||||
case let .directional(up, down, left, right):
|
||||
|
||||
switch input.stringValue
|
||||
{
|
||||
case up.stringValue:
|
||||
itemFrame = CGRect(x: item.frame.minX + item.frame.width / 3,
|
||||
y: item.frame.minY,
|
||||
width: item.frame.width / 3,
|
||||
height: item.frame.height / 3)
|
||||
case down.stringValue:
|
||||
itemFrame = CGRect(x: item.frame.minX + item.frame.width / 3,
|
||||
y: item.frame.minY + (item.frame.height / 3) * 2,
|
||||
width: item.frame.width / 3,
|
||||
height: item.frame.height / 3)
|
||||
|
||||
case left.stringValue:
|
||||
itemFrame = CGRect(x: item.frame.minX,
|
||||
y: item.frame.minY + (item.frame.height / 3),
|
||||
width: item.frame.width / 3,
|
||||
height: item.frame.height / 3)
|
||||
|
||||
case right.stringValue:
|
||||
itemFrame = CGRect(x: item.frame.minX + (item.frame.width / 3) * 2,
|
||||
y: item.frame.minY + (item.frame.height / 3),
|
||||
width: item.frame.width / 3,
|
||||
height: item.frame.height / 3)
|
||||
|
||||
default: itemFrame = nil
|
||||
}
|
||||
}
|
||||
|
||||
if let itemFrame = itemFrame
|
||||
{
|
||||
var presentationFrame = itemFrame.applying(CGAffineTransform(scaleX: controllerView.bounds.width, y: controllerView.bounds.height))
|
||||
presentationFrame = self.view.convert(presentationFrame, from: controllerView)
|
||||
|
||||
return presentationFrame
|
||||
}
|
||||
}
|
||||
else if let index = self.supportedActionInputs.index(where: { $0 == input })
|
||||
{
|
||||
// Input is an ActionInput.
|
||||
|
||||
let indexPath = IndexPath(item: index, section: 0)
|
||||
|
||||
if let attributes = self.actionsMenuViewController.collectionViewLayout.layoutAttributesForItem(at: indexPath)
|
||||
{
|
||||
let presentationFrame = self.view.convert(attributes.frame, from: self.actionsMenuViewController.view)
|
||||
return presentationFrame
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Input is not an on-screen input.
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerInputsViewController: GameControllerReceiver
|
||||
{
|
||||
func gameController(_ gameController: GameController, didActivate controllerInput: DeltaCore.Input)
|
||||
{
|
||||
switch gameController
|
||||
{
|
||||
case self.gameViewController.controllerView:
|
||||
if let calloutView = self.calloutViews[AnyInput(controllerInput)]
|
||||
{
|
||||
self.toggle(calloutView)
|
||||
}
|
||||
|
||||
case self.gameController: self.updateActiveCalloutView(with: controllerInput)
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
func gameController(_ gameController: GameController, didDeactivate input: DeltaCore.Input)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerInputsViewController: SMCalloutViewDelegate
|
||||
{
|
||||
func calloutViewClicked(_ calloutView: SMCalloutView)
|
||||
{
|
||||
guard let calloutView = calloutView as? InputCalloutView else { return }
|
||||
|
||||
self.toggle(calloutView)
|
||||
}
|
||||
}
|
||||
@ -9,37 +9,52 @@
|
||||
import UIKit
|
||||
import DeltaCore
|
||||
|
||||
private enum ControllersSettingsSection: Int
|
||||
import Roxas
|
||||
|
||||
extension ControllersSettingsViewController
|
||||
{
|
||||
fileprivate enum Section: Int
|
||||
{
|
||||
case none
|
||||
case localDevice
|
||||
case externalControllers
|
||||
case customizeControls
|
||||
}
|
||||
}
|
||||
|
||||
private class LocalDeviceController: ExternalController
|
||||
|
||||
private class LocalDeviceController: NSObject, GameController
|
||||
{
|
||||
override var name: String {
|
||||
var name: String {
|
||||
return UIDevice.current.name
|
||||
}
|
||||
|
||||
var playerIndex: Int? {
|
||||
set { Settings.localControllerPlayerIndex = newValue }
|
||||
get { return Settings.localControllerPlayerIndex }
|
||||
}
|
||||
|
||||
let inputType: GameControllerInputType = .standard
|
||||
|
||||
var inputMapping: GameControllerInputMappingProtocol?
|
||||
}
|
||||
|
||||
class ControllersSettingsViewController: UITableViewController
|
||||
{
|
||||
var playerIndex: Int? {
|
||||
didSet
|
||||
{
|
||||
if let playerIndex = self.playerIndex
|
||||
{
|
||||
self.title = NSLocalizedString("Player \(playerIndex + 1)", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
self.title = NSLocalizedString("Controllers", comment: "")
|
||||
}
|
||||
var playerIndex: Int! {
|
||||
didSet {
|
||||
self.title = NSLocalizedString("Player \(self.playerIndex + 1)", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var connectedControllers = ExternalControllerManager.shared.connectedControllers.sorted(by: { $0.playerIndex ?? NSIntegerMax < $1.playerIndex ?? NSIntegerMax })
|
||||
fileprivate var gameController: GameController? {
|
||||
didSet {
|
||||
oldValue?.playerIndex = nil
|
||||
self.gameController?.playerIndex = self.playerIndex
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var connectedControllers = ExternalGameControllerManager.shared.connectedControllers.sorted(by: { $0.playerIndex ?? NSIntegerMax < $1.playerIndex ?? NSIntegerMax })
|
||||
|
||||
fileprivate lazy var localDeviceController: LocalDeviceController = {
|
||||
let device = LocalDeviceController()
|
||||
@ -52,146 +67,76 @@ class ControllersSettingsViewController: UITableViewController
|
||||
{
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalControllerDidConnect(_:)), name: .externalControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalControllerDidDisconnect(_:)), name: .externalControllerDidDisconnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalGameControllerDidConnect(_:)), name: .externalGameControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalGameControllerDidDisconnect(_:)), name: .externalGameControllerDidDisconnect, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
//MARK: - Storyboards -
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||
let gameControllers = [self.localDeviceController as GameController] + self.connectedControllers
|
||||
for gameController in gameControllers
|
||||
{
|
||||
guard let indexPath = self.tableView.indexPathForSelectedRow else { return }
|
||||
|
||||
var controllers = self.connectedControllers
|
||||
controllers.append(self.localDeviceController)
|
||||
|
||||
// Reset previous controller
|
||||
if let playerIndex = self.playerIndex, let index = controllers.index(where: { $0.playerIndex == playerIndex })
|
||||
if gameController.playerIndex == self.playerIndex
|
||||
{
|
||||
let controller = controllers[index]
|
||||
controller.playerIndex = nil
|
||||
self.gameController = gameController
|
||||
break
|
||||
}
|
||||
|
||||
switch ControllersSettingsSection(rawValue: (indexPath as NSIndexPath).section)!
|
||||
{
|
||||
case .none: break
|
||||
case .localDevice: self.localDeviceController.playerIndex = self.playerIndex
|
||||
case .externalControllers:
|
||||
let controller = self.connectedControllers[(indexPath as NSIndexPath).row]
|
||||
controller.playerIndex = self.playerIndex
|
||||
}
|
||||
|
||||
// Updates in case we reset it above, as well as if we updated in the switch statement
|
||||
Settings.localControllerPlayerIndex = self.localDeviceController.playerIndex
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllersSettingsViewController
|
||||
{
|
||||
dynamic func externalControllerDidConnect(_ notification: Notification)
|
||||
{
|
||||
guard let controller = notification.object as? ExternalController else { return }
|
||||
|
||||
if let playerIndex = controller.playerIndex
|
||||
{
|
||||
self.connectedControllers.insert(controller, at: playerIndex)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.connectedControllers.append(controller)
|
||||
}
|
||||
|
||||
if let index = self.connectedControllers.index(of: controller)
|
||||
{
|
||||
if self.connectedControllers.count == 1
|
||||
{
|
||||
self.tableView.insertSections(IndexSet(integer: ControllersSettingsSection.externalControllers.rawValue), with: .fade)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.tableView.insertRows(at: [IndexPath(row: index, section: ControllersSettingsSection.externalControllers.rawValue)], with: .automatic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynamic func externalControllerDidDisconnect(_ notification: Notification)
|
||||
{
|
||||
guard let controller = notification.object as? ExternalController else { return }
|
||||
|
||||
if let index = self.connectedControllers.index(of: controller)
|
||||
{
|
||||
self.connectedControllers.remove(at: index)
|
||||
|
||||
if self.connectedControllers.count == 0
|
||||
{
|
||||
self.tableView.deleteSections(IndexSet(integer: ControllersSettingsSection.externalControllers.rawValue), with: .fade)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.tableView.deleteRows(at: [IndexPath(row: index, section: ControllersSettingsSection.externalControllers.rawValue)], with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
if controller.playerIndex == self.playerIndex
|
||||
{
|
||||
self.tableView.reloadSections(IndexSet(integer: ControllersSettingsSection.none.rawValue), with: .none)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllersSettingsViewController
|
||||
{
|
||||
override func numberOfSections(in tableView: UITableView) -> Int
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||
{
|
||||
if self.connectedControllers.count == 0
|
||||
{
|
||||
return 2
|
||||
}
|
||||
guard let identifier = segue.identifier else { return }
|
||||
|
||||
return 3
|
||||
}
|
||||
switch identifier
|
||||
{
|
||||
case "controllerInputsSegue":
|
||||
let controllerInputsViewController = (segue.destination as! UINavigationController).topViewController as! ControllerInputsViewController
|
||||
controllerInputsViewController.gameController = self.gameController
|
||||
controllerInputsViewController.system = .snes
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
{
|
||||
switch ControllersSettingsSection(rawValue: section)!
|
||||
{
|
||||
case .none: return 1
|
||||
case .localDevice: return 1
|
||||
case .externalControllers: return self.connectedControllers.count
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
|
||||
@IBAction private func unwindFromControllerControlsViewController(_ segue: UIStoryboardSegue)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllersSettingsViewController
|
||||
{
|
||||
func configure(_ cell: UITableViewCell, for indexPath: IndexPath)
|
||||
{
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
|
||||
cell.detailTextLabel?.text = nil
|
||||
cell.accessoryType = .none
|
||||
cell.detailTextLabel?.text = nil
|
||||
cell.textLabel?.textColor = .darkText
|
||||
|
||||
if (indexPath as NSIndexPath).section == ControllersSettingsSection.none.rawValue
|
||||
switch Section(rawValue: indexPath.section)!
|
||||
{
|
||||
case .none:
|
||||
cell.textLabel?.text = NSLocalizedString("None", comment: "")
|
||||
|
||||
if Settings.localControllerPlayerIndex != self.playerIndex && !self.connectedControllers.contains(where: { $0.playerIndex == self.playerIndex })
|
||||
{
|
||||
cell.accessoryType = .checkmark
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
let controller: ExternalController
|
||||
|
||||
if (indexPath as NSIndexPath).section == ControllersSettingsSection.localDevice.rawValue
|
||||
case .localDevice, .externalControllers:
|
||||
let controller: GameController
|
||||
|
||||
if indexPath.section == Section.localDevice.rawValue
|
||||
{
|
||||
controller = self.localDeviceController
|
||||
}
|
||||
else if (indexPath as NSIndexPath).section == ControllersSettingsSection.externalControllers.rawValue
|
||||
else if indexPath.section == Section.externalControllers.rawValue
|
||||
{
|
||||
controller = self.connectedControllers[(indexPath as NSIndexPath).row]
|
||||
controller = self.connectedControllers[indexPath.row]
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -210,20 +155,177 @@ extension ControllersSettingsViewController
|
||||
{
|
||||
cell.detailTextLabel?.text = NSLocalizedString("Player \(playerIndex + 1)", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
case .customizeControls:
|
||||
cell.textLabel?.text = NSLocalizedString("Customize Controls…", comment: "")
|
||||
cell.textLabel?.textColor = self.view.tintColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllersSettingsViewController
|
||||
{
|
||||
dynamic func externalGameControllerDidConnect(_ notification: Notification)
|
||||
{
|
||||
guard let controller = notification.object as? GameController else { return }
|
||||
|
||||
if let playerIndex = controller.playerIndex
|
||||
{
|
||||
// Keep connected controllers sorted.
|
||||
|
||||
self.connectedControllers.insert(controller, at: playerIndex)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.connectedControllers.append(controller)
|
||||
}
|
||||
|
||||
if let index = self.connectedControllers.index(where: { $0 == controller })
|
||||
{
|
||||
if self.connectedControllers.count == 1
|
||||
{
|
||||
self.tableView.insertSections(IndexSet(integer: Section.externalControllers.rawValue), with: .fade)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.tableView.insertRows(at: [IndexPath(row: index, section: Section.externalControllers.rawValue)], with: .automatic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynamic func externalGameControllerDidDisconnect(_ notification: Notification)
|
||||
{
|
||||
guard let controller = notification.object as? GameController else { return }
|
||||
|
||||
if let index = self.connectedControllers.index(where: { $0 == controller })
|
||||
{
|
||||
self.connectedControllers.remove(at: index)
|
||||
|
||||
if self.connectedControllers.count == 0
|
||||
{
|
||||
self.tableView.deleteSections(IndexSet(integer: Section.externalControllers.rawValue), with: .fade)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.tableView.deleteRows(at: [IndexPath(row: index, section: Section.externalControllers.rawValue)], with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
if controller.playerIndex == self.playerIndex
|
||||
{
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.none.rawValue), with: .none)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllersSettingsViewController
|
||||
{
|
||||
override func numberOfSections(in tableView: UITableView) -> Int
|
||||
{
|
||||
if self.connectedControllers.count == 0
|
||||
{
|
||||
return 2
|
||||
}
|
||||
|
||||
if self.gameController == nil || Settings.localControllerPlayerIndex == self.playerIndex
|
||||
{
|
||||
return 3
|
||||
}
|
||||
|
||||
return 4
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
{
|
||||
switch Section(rawValue: section)!
|
||||
{
|
||||
case .none: return 1
|
||||
case .localDevice: return 1
|
||||
case .externalControllers: return self.connectedControllers.count
|
||||
case .customizeControls: return 1
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
|
||||
{
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: RSTCellContentGenericCellIdentifier, for: indexPath)
|
||||
|
||||
self.configure(cell, for: indexPath)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
|
||||
{
|
||||
switch ControllersSettingsSection(rawValue: section)!
|
||||
switch Section(rawValue: section)!
|
||||
{
|
||||
case .none: return nil
|
||||
case .localDevice: return NSLocalizedString("Local Device", comment: "")
|
||||
case .externalControllers: return self.connectedControllers.count > 0 ? NSLocalizedString("External Controllers", comment: "") : ""
|
||||
case .customizeControls: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllersSettingsViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||
{
|
||||
let previousIndexPath: IndexPath?
|
||||
|
||||
if let gameController = self.gameController
|
||||
{
|
||||
if gameController == self.localDeviceController
|
||||
{
|
||||
previousIndexPath = IndexPath(row: 0, section: Section.localDevice.rawValue)
|
||||
}
|
||||
else if let row = self.connectedControllers.index(where: { $0 == gameController })
|
||||
{
|
||||
previousIndexPath = IndexPath(row: row, section: Section.externalControllers.rawValue)
|
||||
}
|
||||
else
|
||||
{
|
||||
previousIndexPath = nil
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
previousIndexPath = IndexPath(row: 0, section: Section.none.rawValue)
|
||||
}
|
||||
|
||||
switch Section(rawValue: indexPath.section)!
|
||||
{
|
||||
case .none: self.gameController = nil
|
||||
case .localDevice: self.gameController = self.localDeviceController
|
||||
case .externalControllers: self.gameController = self.connectedControllers[indexPath.row]
|
||||
case .customizeControls:
|
||||
guard let cell = tableView.cellForRow(at: indexPath) else { return }
|
||||
self.performSegue(withIdentifier: "controllerInputsSegue", sender: cell)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.tableView.beginUpdates()
|
||||
|
||||
if let previousIndexPath = previousIndexPath, let cell = tableView.cellForRow(at: previousIndexPath)
|
||||
{
|
||||
// Must configure cell directly, or else a strange animation occurs when reloading row on iOS 11.
|
||||
self.configure(cell, for: previousIndexPath)
|
||||
}
|
||||
|
||||
self.tableView.reloadRows(at: [indexPath], with: .none)
|
||||
|
||||
|
||||
if self.numberOfSections(in: self.tableView) > self.tableView.numberOfSections
|
||||
{
|
||||
self.tableView.insertSections(IndexSet(integer: Section.customizeControls.rawValue), with: .fade)
|
||||
}
|
||||
else if self.numberOfSections(in: self.tableView) < self.tableView.numberOfSections
|
||||
{
|
||||
self.tableView.deleteSections(IndexSet(integer: Section.customizeControls.rawValue), with: .fade)
|
||||
}
|
||||
|
||||
self.tableView.endUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
84
Delta/Settings/Controllers/InputCalloutView.swift
Normal file
84
Delta/Settings/Controllers/InputCalloutView.swift
Normal file
@ -0,0 +1,84 @@
|
||||
//
|
||||
// InputCalloutView.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 7/9/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import SMCalloutView
|
||||
|
||||
import DeltaCore
|
||||
|
||||
extension InputCalloutView
|
||||
{
|
||||
enum State
|
||||
{
|
||||
case normal
|
||||
case listening
|
||||
}
|
||||
}
|
||||
|
||||
class InputCalloutView: SMCalloutView
|
||||
{
|
||||
var input: Input? {
|
||||
didSet {
|
||||
self.updateState()
|
||||
}
|
||||
}
|
||||
|
||||
var state: State = .normal {
|
||||
didSet {
|
||||
self.updateState()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let textLabel: UILabel
|
||||
|
||||
init()
|
||||
{
|
||||
self.textLabel = UILabel()
|
||||
self.textLabel.font = UIFont.boldSystemFont(ofSize: 18.0)
|
||||
self.textLabel.textAlignment = .center
|
||||
|
||||
super.init(frame: CGRect.zero)
|
||||
|
||||
self.titleView = self.textLabel
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func tintColorDidChange()
|
||||
{
|
||||
super.tintColorDidChange()
|
||||
|
||||
self.updateTintColor()
|
||||
}
|
||||
}
|
||||
|
||||
private extension InputCalloutView
|
||||
{
|
||||
func updateState()
|
||||
{
|
||||
switch self.state
|
||||
{
|
||||
case .normal: self.textLabel.text = self.input?.localizedName
|
||||
case .listening: self.textLabel.text = NSLocalizedString("Press Button", comment: "")
|
||||
}
|
||||
|
||||
self.updateTintColor()
|
||||
|
||||
self.textLabel.sizeToFit()
|
||||
}
|
||||
|
||||
func updateTintColor()
|
||||
{
|
||||
switch self.state
|
||||
{
|
||||
case .normal: self.textLabel.textColor = self.tintColor
|
||||
case .listening: self.textLabel.textColor = .red
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ssH-mM-uG6">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13196" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ssH-mM-uG6">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13173"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@ -29,7 +29,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="tls-Hv-Rx2">
|
||||
<rect key="frame" x="15" y="12" width="56" height="19.5"/>
|
||||
<rect key="frame" x="16" y="12" width="56" height="19.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -53,7 +53,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="e3u-x9-IEC">
|
||||
<rect key="frame" x="15" y="12" width="58" height="19.5"/>
|
||||
<rect key="frame" x="16" y="12" width="58" height="19.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -77,7 +77,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 3" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Cdn-11-xZe">
|
||||
<rect key="frame" x="15" y="12" width="58.5" height="19.5"/>
|
||||
<rect key="frame" x="16" y="12" width="58.5" height="19.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -101,7 +101,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 4" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hls-3b-EaS">
|
||||
<rect key="frame" x="15" y="12" width="59" height="19.5"/>
|
||||
<rect key="frame" x="16" y="12" width="59" height="19.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -129,7 +129,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="System Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="mBC-YU-BVK">
|
||||
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
|
||||
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -146,7 +146,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="System Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Dxs-Me-IVU">
|
||||
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
|
||||
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -163,7 +163,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="System Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="o9x-Kn-6bC">
|
||||
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
|
||||
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -184,7 +184,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="whi-If-wFf">
|
||||
<rect key="frame" x="74" y="7" width="288" height="31"/>
|
||||
<rect key="frame" x="75" y="7" width="286" height="31"/>
|
||||
<connections>
|
||||
<action selector="beginChangingControllerOpacityWith:" destination="eHi-aO-uGS" eventType="touchDown" id="NG9-FX-62d"/>
|
||||
<action selector="changeControllerOpacityWith:" destination="eHi-aO-uGS" eventType="valueChanged" id="Zci-tN-4uU"/>
|
||||
@ -194,7 +194,7 @@
|
||||
</connections>
|
||||
</slider>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="50%" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zaz-yD-CYG">
|
||||
<rect key="frame" x="15" y="11" width="46" height="21"/>
|
||||
<rect key="frame" x="16" y="11" width="46" height="21"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="21" id="ACD-qY-k0J"/>
|
||||
<constraint firstAttribute="width" constant="46" id="ZVd-ie-qRm"/>
|
||||
@ -240,6 +240,78 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1555" y="471"/>
|
||||
</scene>
|
||||
<!--Controls-->
|
||||
<scene sceneID="Gi9-m1-y9x">
|
||||
<objects>
|
||||
<viewController title="Controls" id="x1g-pH-DnF" customClass="ControllerInputsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="cH1-gu-g2u"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="Z6c-bc-h6l"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="cPg-qa-ERT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6Wl-el-X30" userLabel="GameViewController">
|
||||
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
|
||||
<connections>
|
||||
<segue destination="LIv-AL-s86" kind="embed" identifier="embedGameViewController" id="2Qg-Jw-0mM"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" placeholderIntrinsicWidth="375" placeholderIntrinsicHeight="200" translatesAutoresizingMaskIntoConstraints="NO" id="KkE-ji-6Y8" userLabel="GridMenuViewController">
|
||||
<rect key="frame" x="0.0" y="233.5" width="375" height="200"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="200" id="MWA-T4-ROi"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<segue destination="Jpj-e9-6XW" kind="embed" identifier="embedActionsMenuViewController" id="kfu-fO-l6Z"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstItem="KkE-ji-6Y8" firstAttribute="centerY" secondItem="cPg-qa-ERT" secondAttribute="centerY" placeholder="YES" id="4wi-cL-aCQ"/>
|
||||
<constraint firstItem="Z6c-bc-h6l" firstAttribute="top" secondItem="6Wl-el-X30" secondAttribute="bottom" id="Bmp-yB-Yf1"/>
|
||||
<constraint firstAttribute="trailing" secondItem="KkE-ji-6Y8" secondAttribute="trailing" id="Jeb-8K-VYw"/>
|
||||
<constraint firstItem="6Wl-el-X30" firstAttribute="top" secondItem="cH1-gu-g2u" secondAttribute="bottom" id="TD2-bx-DJC"/>
|
||||
<constraint firstAttribute="trailing" secondItem="6Wl-el-X30" secondAttribute="trailing" id="Xph-DL-tBk"/>
|
||||
<constraint firstItem="6Wl-el-X30" firstAttribute="leading" secondItem="cPg-qa-ERT" secondAttribute="leading" id="gcd-77-5wR"/>
|
||||
<constraint firstItem="KkE-ji-6Y8" firstAttribute="leading" secondItem="cPg-qa-ERT" secondAttribute="leading" id="z7N-Cn-hGs"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="4p8-OB-LsR" appends="YES" id="4k4-Oj-XtP"/>
|
||||
</connections>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="UeP-Yr-9jA">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="QfC-sf-WbP">
|
||||
<connections>
|
||||
<segue destination="8l5-7I-Z7e" kind="unwind" identifier="cancelControllerControls" unwindAction="unwindFromControllerControlsViewController:" id="AkD-Lu-h5b"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" style="done" systemItem="save" id="WHh-7W-jpl">
|
||||
<connections>
|
||||
<segue destination="8l5-7I-Z7e" kind="unwind" identifier="saveControllerControls" unwindAction="unwindFromControllerControlsViewController:" id="4xr-OB-4dx"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" barStyle="black" prompted="NO"/>
|
||||
<connections>
|
||||
<outlet property="actionsMenuViewControllerHeightConstraint" destination="MWA-T4-ROi" id="itx-dZ-m76"/>
|
||||
<outlet property="cancelTapGestureRecognizer" destination="4p8-OB-LsR" id="coO-FL-pbp"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="DqP-Jn-rth" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="8l5-7I-Z7e" userLabel="Exit" sceneMemberID="exit"/>
|
||||
<tapGestureRecognizer delaysTouchesBegan="YES" id="4p8-OB-LsR">
|
||||
<connections>
|
||||
<action selector="handleTapGesture:" destination="x1g-pH-DnF" id="8KO-75-4Iy"/>
|
||||
<outlet property="delegate" destination="x1g-pH-DnF" id="GDY-v6-naf"/>
|
||||
</connections>
|
||||
</tapGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3809" y="471"/>
|
||||
</scene>
|
||||
<!--Controllers-->
|
||||
<scene sceneID="swa-DT-VKS">
|
||||
<objects>
|
||||
@ -257,7 +329,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Controller Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="VBO-V1-Wfu">
|
||||
<rect key="frame" x="15" y="12" width="118.5" height="19.5"/>
|
||||
<rect key="frame" x="16" y="12" width="118.5" height="19.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -272,9 +344,6 @@
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="HzL-SB-qry" kind="unwind" identifier="unwindControllersSegue" unwindAction="unwindFromControllersSettingsViewController:" id="WJe-ZI-clo"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
@ -284,9 +353,11 @@
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Controllers" id="QK7-oi-2jJ"/>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
<connections>
|
||||
<segue destination="0QR-U9-gtx" kind="presentation" identifier="controllerInputsSegue" id="E3Y-yV-zT5"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="owG-Kh-rfn" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="HzL-SB-qry" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2221" y="471"/>
|
||||
</scene>
|
||||
@ -416,7 +487,7 @@
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="ssH-mM-uG6" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="Ckw-ES-lkE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@ -428,5 +499,76 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="889" y="471"/>
|
||||
</scene>
|
||||
<!--Grid Menu View Controller-->
|
||||
<scene sceneID="Lgi-Ii-M1W">
|
||||
<objects>
|
||||
<collectionViewController id="Jpj-e9-6XW" customClass="GridMenuViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="yGk-jU-wZQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="tLr-UM-1BH" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
|
||||
<size key="itemSize" width="50" height="50"/>
|
||||
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="Hef-IR-nMO" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
</collectionViewCell>
|
||||
</cells>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="Jpj-e9-6XW" id="iAK-8A-KXA"/>
|
||||
<outlet property="delegate" destination="Jpj-e9-6XW" id="sbi-az-9kr"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
<size key="freeformSize" width="375" height="667"/>
|
||||
</collectionViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="pRg-BA-3KK" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="4566" y="-216"/>
|
||||
</scene>
|
||||
<!--Game View Controller-->
|
||||
<scene sceneID="qAz-yz-iOc">
|
||||
<objects>
|
||||
<viewController id="LIv-AL-s86" customClass="GameViewController" customModule="DeltaCore" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="9u3-RP-Qcj"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="XGZ-ro-kQv"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="57g-cn-rbZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="603"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="uQK-ch-9AG" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="4566" y="471"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="bwW-s2-fcE">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="0QR-U9-gtx" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" barStyle="black" prompted="NO"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" id="Y5H-O6-CQ5">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="x1g-pH-DnF" kind="relationship" relationship="rootViewController" id="EOa-ao-vBI"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="D4f-Fb-zfa" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2977" y="471"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
||||
@ -46,8 +46,8 @@ class SettingsViewController: UITableViewController
|
||||
{
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalControllerDidConnect(_:)), name: .externalControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalControllerDidDisconnect(_:)), name: .externalControllerDidDisconnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidConnect(_:)), name: .externalGameControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidDisconnect(_:)), name: .externalGameControllerDidDisconnect, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
@ -64,6 +64,13 @@ class SettingsViewController: UITableViewController
|
||||
|
||||
if let indexPath = self.tableView.indexPathForSelectedRow
|
||||
{
|
||||
if indexPath.section == Section.controllers.rawValue
|
||||
{
|
||||
// Update and temporarily re-select selected row.
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.controllers.rawValue), with: .none)
|
||||
self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none)
|
||||
}
|
||||
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
@ -111,18 +118,6 @@ private extension SettingsViewController
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsViewController
|
||||
{
|
||||
@IBAction func unwindFromControllersSettingsViewController(_ segue: UIStoryboardSegue)
|
||||
{
|
||||
let indexPath = self.tableView.indexPathForSelectedRow
|
||||
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.controllers.rawValue), with: .none)
|
||||
|
||||
self.tableView.selectRow(at: indexPath, animated: true, scrollPosition: UITableViewScrollPosition.none)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsViewController
|
||||
{
|
||||
@IBAction func beginChangingControllerOpacity(with sender: UISlider)
|
||||
@ -154,12 +149,12 @@ private extension SettingsViewController
|
||||
|
||||
private extension SettingsViewController
|
||||
{
|
||||
dynamic func externalControllerDidConnect(_ notification: Notification)
|
||||
dynamic func externalGameControllerDidConnect(_ notification: Notification)
|
||||
{
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.controllers.rawValue), with: .none)
|
||||
}
|
||||
|
||||
dynamic func externalControllerDidDisconnect(_ notification: Notification)
|
||||
dynamic func externalGameControllerDidDisconnect(_ notification: Notification)
|
||||
{
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.controllers.rawValue), with: .none)
|
||||
}
|
||||
@ -190,9 +185,9 @@ extension SettingsViewController
|
||||
{
|
||||
cell.detailTextLabel?.text = UIDevice.current.name
|
||||
}
|
||||
else if let index = ExternalControllerManager.shared.connectedControllers.index(where: { $0.playerIndex == indexPath.row })
|
||||
else if let index = ExternalGameControllerManager.shared.connectedControllers.index(where: { $0.playerIndex == indexPath.row })
|
||||
{
|
||||
let controller = ExternalControllerManager.shared.connectedControllers[index]
|
||||
let controller = ExternalGameControllerManager.shared.connectedControllers[index]
|
||||
cell.detailTextLabel?.text = controller.name
|
||||
}
|
||||
else
|
||||
|
||||
1
Podfile
1
Podfile
@ -9,6 +9,7 @@ target 'Delta' do
|
||||
pod 'SDWebImage', '~> 3.8'
|
||||
pod 'Fabric', '~> 1.6.0'
|
||||
pod 'Crashlytics', '~> 3.8.0'
|
||||
pod 'SMCalloutView'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
|
||||
@ -6,6 +6,7 @@ PODS:
|
||||
- SDWebImage (3.8.2):
|
||||
- SDWebImage/Core (= 3.8.2)
|
||||
- SDWebImage/Core (3.8.2)
|
||||
- SMCalloutView (2.1.5)
|
||||
- SQLite.swift (0.11.3):
|
||||
- SQLite.swift/standard (= 0.11.3)
|
||||
- SQLite.swift/standard (0.11.3)
|
||||
@ -15,6 +16,7 @@ DEPENDENCIES:
|
||||
- Fabric (~> 1.6.0)
|
||||
- FileMD5Hash (~> 2.0.0)
|
||||
- SDWebImage (~> 3.8)
|
||||
- SMCalloutView
|
||||
- SQLite.swift (~> 0.11.0)
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
@ -22,8 +24,9 @@ SPEC CHECKSUMS:
|
||||
Fabric: 5911403591946b8228ab1c51d98f1d7137e863c6
|
||||
FileMD5Hash: 3ed69cc19a21ff4d30ae8833fc104275ad2c7de0
|
||||
SDWebImage: '098e97e6176540799c27e804c96653ee0833d13c'
|
||||
SMCalloutView: 5c0ee363dc8e7204b2fda17dfad38c93e9e23481
|
||||
SQLite.swift: 99b36c22084427f0abbeb957556ce1528cf10bb3
|
||||
|
||||
PODFILE CHECKSUM: de6e2bf57dcf8e9fe6b622b181fd87d3855641e6
|
||||
PODFILE CHECKSUM: 598f830560ac5b18bbe0eb40134a1719f38f12f1
|
||||
|
||||
COCOAPODS: 1.2.1
|
||||
|
||||
5
Pods/Manifest.lock
generated
5
Pods/Manifest.lock
generated
@ -6,6 +6,7 @@ PODS:
|
||||
- SDWebImage (3.8.2):
|
||||
- SDWebImage/Core (= 3.8.2)
|
||||
- SDWebImage/Core (3.8.2)
|
||||
- SMCalloutView (2.1.5)
|
||||
- SQLite.swift (0.11.3):
|
||||
- SQLite.swift/standard (= 0.11.3)
|
||||
- SQLite.swift/standard (0.11.3)
|
||||
@ -15,6 +16,7 @@ DEPENDENCIES:
|
||||
- Fabric (~> 1.6.0)
|
||||
- FileMD5Hash (~> 2.0.0)
|
||||
- SDWebImage (~> 3.8)
|
||||
- SMCalloutView
|
||||
- SQLite.swift (~> 0.11.0)
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
@ -22,8 +24,9 @@ SPEC CHECKSUMS:
|
||||
Fabric: 5911403591946b8228ab1c51d98f1d7137e863c6
|
||||
FileMD5Hash: 3ed69cc19a21ff4d30ae8833fc104275ad2c7de0
|
||||
SDWebImage: '098e97e6176540799c27e804c96653ee0833d13c'
|
||||
SMCalloutView: 5c0ee363dc8e7204b2fda17dfad38c93e9e23481
|
||||
SQLite.swift: 99b36c22084427f0abbeb957556ce1528cf10bb3
|
||||
|
||||
PODFILE CHECKSUM: de6e2bf57dcf8e9fe6b622b181fd87d3855641e6
|
||||
PODFILE CHECKSUM: 598f830560ac5b18bbe0eb40134a1719f38f12f1
|
||||
|
||||
COCOAPODS: 1.2.1
|
||||
|
||||
1410
Pods/Pods.xcodeproj/project.pbxproj
generated
1410
Pods/Pods.xcodeproj/project.pbxproj
generated
File diff suppressed because it is too large
Load Diff
176
Pods/SMCalloutView/LICENSE
generated
Normal file
176
Pods/SMCalloutView/LICENSE
generated
Normal file
@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
99
Pods/SMCalloutView/README.md
generated
Normal file
99
Pods/SMCalloutView/README.md
generated
Normal file
@ -0,0 +1,99 @@
|
||||

|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
SMCalloutView aims to be an exact replica of the private UICalloutView system
|
||||
control.
|
||||
|
||||
We all love those "bubbles" you get when clicking pins in MKMapView. But
|
||||
sadly, it's impossible to present this bubble-style "Callout" UI anywhere
|
||||
outside MKMapView. Phooey! So this class _painstakingly_ recreates this handy
|
||||
control for your pleasure.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To use SMCalloutView in your own projects, simply copy the files
|
||||
`SMCalloutView.h` and `SMCalloutView.m`.
|
||||
|
||||
SMCalloutView, by default, will render in the new style introduced with
|
||||
iOS 7. If you need the old style, simply include `SMClassicCalloutView.h`
|
||||
and `SMClassicCalloutView.m` in your project as well. There is a special
|
||||
class constructor, `+[SMCalloutView platformCalloutView]` which will
|
||||
automatically select the appropriate callout class for the current platform.
|
||||
|
||||
The comments in `SMCalloutView.h` do a lot of explaining on how to use the
|
||||
class, but the main function you'll need is `presentCalloutFromRect:`. You'll
|
||||
specify the view you'd like to add the callout to, as well as the rect
|
||||
defining the "target" that the popup should point at. The target rect should
|
||||
be _in the coordinate system of the target view_ (just like the similarly-
|
||||
named `UIPopover` method). Most likely this will be `target.frame` if you're
|
||||
adding the callout view as a sibling of the target view, or it would be
|
||||
`target.bounds` if you're adding the callout view to the target itself.
|
||||
|
||||
You can study the included project's UIViewController subclasses for a working
|
||||
example.
|
||||
|
||||
|
||||
Questions
|
||||
---------
|
||||
|
||||
#### How do I change the height of the callout?
|
||||
|
||||
If you use only the `title/titleView/subtitle/subtitleView` properties, the
|
||||
callout will always be the "system standard" height. If you assign the
|
||||
`contentView` property however, then the callout will size to fit the
|
||||
`contentView` and the other properties are ignored.
|
||||
|
||||
[#29]: https://github.com/nfarina/calloutview/issues/29
|
||||
|
||||
|
||||
#### Can I customize the background graphics?
|
||||
|
||||
Yes, the callout background is an instance of `SMCalloutBackgroundView`. You
|
||||
can set your own custom `View` subclass to be the background, or you can use
|
||||
one of the built-in subclasses:
|
||||
|
||||
- `SMCalloutMaskedBackgroundView` renders an iOS-7 style background.
|
||||
|
||||
- `SMCalloutImageBackgroundView` lets you specify each of the image
|
||||
"fragments" that make up a horizontally-stretchable background.
|
||||
|
||||
- `SMCalloutDrawnBackgroundView` draws the background at any size using
|
||||
CoreGraphics methods. You can copy the `-drawRect` method and change the
|
||||
parameters to suit your needs.
|
||||
|
||||
|
||||
#### Can I use the callout with the Google Maps iOS SDK?
|
||||
|
||||
Check out [ryanmaxwell's demo project][googlemaps] for an example of one way
|
||||
to do this. ([More discussion on this topic][#25])
|
||||
|
||||
[googlemaps]: https://github.com/ryanmaxwell/GoogleMapsCalloutView
|
||||
[#25]: https://github.com/nfarina/calloutview/issues/25
|
||||
|
||||
|
||||
#### Have you recreated more of MapKit?
|
||||
|
||||
Nope, but other intrepid coders have!
|
||||
|
||||
- For an awesome replacement of the pulsing blue "Current Location" dot, check
|
||||
out [Sam Vermette's SVPulsingAnnotationView][dot].
|
||||
|
||||
- And for the outdoor map data and tiles themselves, check out [Mapbox's iOS
|
||||
SDK][mapbox], a complete open-source solution for custom maps. They even use
|
||||
`SMCalloutView` out of the box!
|
||||
|
||||
[dot]: https://github.com/samvermette/SVPulsingAnnotationView
|
||||
[mapbox]: https://www.mapbox.com/mobile/
|
||||
|
||||
|
||||
More Info
|
||||
---------
|
||||
|
||||
You can read more info if you wish in the [blog post][].
|
||||
|
||||
[blog post]: http://nfarina.com/post/78014139253/smcalloutview-for-ios-7
|
||||
200
Pods/SMCalloutView/SMCalloutView.h
generated
Executable file
200
Pods/SMCalloutView/SMCalloutView.h
generated
Executable file
@ -0,0 +1,200 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
/*
|
||||
|
||||
SMCalloutView
|
||||
-------------
|
||||
Created by Nick Farina (nfarina@gmail.com)
|
||||
Version 2.1.5
|
||||
|
||||
*/
|
||||
|
||||
/// options for which directions the callout is allowed to "point" in.
|
||||
typedef NS_OPTIONS(NSUInteger, SMCalloutArrowDirection) {
|
||||
SMCalloutArrowDirectionUp = 1 << 0,
|
||||
SMCalloutArrowDirectionDown = 1 << 1,
|
||||
SMCalloutArrowDirectionAny = SMCalloutArrowDirectionUp | SMCalloutArrowDirectionDown
|
||||
};
|
||||
|
||||
/// options for the callout present/dismiss animation
|
||||
typedef NS_ENUM(NSInteger, SMCalloutAnimation) {
|
||||
/// the "bounce" animation we all know and love from @c UIAlertView
|
||||
SMCalloutAnimationBounce,
|
||||
/// a simple fade in or out
|
||||
SMCalloutAnimationFade,
|
||||
/// grow or shrink linearly, like in the iPad Calendar app
|
||||
SMCalloutAnimationStretch
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// when delaying our popup in order to scroll content into view, you can use this amount to match the
|
||||
/// animation duration of UIScrollView when using @c -setContentOffset:animated.
|
||||
extern NSTimeInterval const kSMCalloutViewRepositionDelayForUIScrollView;
|
||||
|
||||
@protocol SMCalloutViewDelegate;
|
||||
@class SMCalloutBackgroundView;
|
||||
|
||||
//
|
||||
// Callout view.
|
||||
//
|
||||
|
||||
@interface SMCalloutView : UIView
|
||||
|
||||
@property (nonatomic, weak, nullable) id<SMCalloutViewDelegate> delegate;
|
||||
/// title/titleView relationship mimics UINavigationBar.
|
||||
@property (nonatomic, copy, nullable) NSString *title;
|
||||
@property (nonatomic, copy, nullable) NSString *subtitle;
|
||||
|
||||
/// Left accessory view for the call out
|
||||
@property (nonatomic, strong, nullable) UIView *leftAccessoryView;
|
||||
/// Right accessoty view for the call out
|
||||
@property (nonatomic, strong, nullable) UIView *rightAccessoryView;
|
||||
/// Default @c SMCalloutArrowDirectionDown
|
||||
@property (nonatomic, assign) SMCalloutArrowDirection permittedArrowDirection;
|
||||
/// The current arrow direction
|
||||
@property (nonatomic, readonly) SMCalloutArrowDirection currentArrowDirection;
|
||||
/// if the @c UIView you're constraining to has portions that are overlapped by nav bar, tab bar, etc. you'll need to tell us those insets.
|
||||
@property (nonatomic, assign) UIEdgeInsets constrainedInsets;
|
||||
/// default is @c SMCalloutMaskedBackgroundView, or @c SMCalloutDrawnBackgroundView when using @c SMClassicCalloutView
|
||||
@property (nonatomic, strong) SMCalloutBackgroundView *backgroundView;
|
||||
|
||||
/**
|
||||
@brief Custom title view.
|
||||
|
||||
@disucssion Keep in mind that @c SMCalloutView calls @c -sizeThatFits on titleView/subtitleView if defined, so your view
|
||||
may be resized as a result of that (especially if you're using @c UILabel/UITextField). You may want to subclass and override @c -sizeThatFits, or just wrap your view in a "generic" @c UIView if you do not want it to be auto-sized.
|
||||
|
||||
@warning If this is set, the respective @c title property will be ignored.
|
||||
*/
|
||||
@property (nonatomic, strong, nullable) UIView *titleView;
|
||||
|
||||
/**
|
||||
@brief Custom subtitle view.
|
||||
|
||||
@discussion Keep in mind that @c SMCalloutView calls @c -sizeThatFits on subtitleView if defined, so your view
|
||||
may be resized as a result of that (especially if you're using @c UILabel/UITextField). You may want to subclass and override @c -sizeThatFits, or just wrap your view in a "generic" @c UIView if you do not want it to be auto-sized.
|
||||
|
||||
@warning If this is set, the respective @c subtitle property will be ignored.
|
||||
*/
|
||||
@property (nonatomic, strong, nullable) UIView *subtitleView;
|
||||
|
||||
/// Custom "content" view that can be any width/height. If this is set, title/subtitle/titleView/subtitleView are all ignored.
|
||||
@property (nonatomic, retain, nullable) UIView *contentView;
|
||||
|
||||
/// Custom content view margin
|
||||
@property (nonatomic, assign) UIEdgeInsets contentViewInset;
|
||||
|
||||
/// calloutOffset is the offset in screen points from the top-middle of the target view, where the anchor of the callout should be shown.
|
||||
@property (nonatomic, assign) CGPoint calloutOffset;
|
||||
|
||||
/// default SMCalloutAnimationBounce, SMCalloutAnimationFade respectively
|
||||
@property (nonatomic, assign) SMCalloutAnimation presentAnimation, dismissAnimation;
|
||||
|
||||
/// Returns a new instance of SMCalloutView if running on iOS 7 or better, otherwise a new instance of SMClassicCalloutView if available.
|
||||
+ (SMCalloutView *)platformCalloutView;
|
||||
|
||||
/**
|
||||
@brief Presents a callout view by adding it to "inView" and pointing at the given rect of inView's bounds.
|
||||
|
||||
@discussion Constrains the callout to the bounds of the given view. Optionally scrolls the given rect into view (plus margins)
|
||||
if @c -delegate is set and responds to @c -delayForRepositionWithSize.
|
||||
|
||||
@param rect @c CGRect to present the view from
|
||||
@param view view to 'constrain' the @c constrainedView to
|
||||
@param constrainedView @c UIView to be constrainted in @c view
|
||||
@param animated @c BOOL if presentation should be animated
|
||||
*/
|
||||
- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated;
|
||||
|
||||
/**
|
||||
@brief Present a callout layer in the `layer` and pointing at the given rect of the `layer` bounds
|
||||
|
||||
@discussion Same as the view-based presentation, but inserts the callout into a CALayer hierarchy instead.
|
||||
@note Be aware that you'll have to direct your own touches to any accessory views, since CALayer doesn't relay touch events.
|
||||
|
||||
@param rect @c CGRect to present the view from
|
||||
@param layer layer to 'constrain' the @c constrainedLayer to
|
||||
@param constrainedLayer @c CALayer to be constrained in @c layer
|
||||
@param animated @c BOOL if presentation should be animated
|
||||
*/
|
||||
- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer constrainedToLayer:(CALayer *)constrainedLayer animated:(BOOL)animated;
|
||||
|
||||
/**
|
||||
Dismiss the callout view
|
||||
|
||||
@param animated @c BOOL if dismissal should be animated
|
||||
*/
|
||||
- (void)dismissCalloutAnimated:(BOOL)animated;
|
||||
|
||||
/// For subclassers. You can override this method to provide your own custom animation for presenting/dismissing the callout.
|
||||
- (CAAnimation *)animationWithType:(SMCalloutAnimation)type presenting:(BOOL)presenting;
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// Background view - default draws the iOS 7 system background style (translucent white with rounded arrow).
|
||||
//
|
||||
|
||||
/// Abstract base class
|
||||
@interface SMCalloutBackgroundView : UIView
|
||||
/// indicates where the tip of the arrow should be drawn, as a pixel offset
|
||||
@property (nonatomic, assign) CGPoint arrowPoint;
|
||||
/// will be set by the callout when the callout is in a highlighted state
|
||||
@property (nonatomic, assign) BOOL highlighted;
|
||||
/// returns an optional layer whose contents should mask the callout view's contents (not honored by @c SMClassicCalloutView )
|
||||
@property (nonatomic, assign) CALayer *contentMask;
|
||||
/// height of the callout "arrow"
|
||||
@property (nonatomic, assign) CGFloat anchorHeight;
|
||||
/// the smallest possible distance from the edge of our control to the "tip" of the anchor, from either left or right
|
||||
@property (nonatomic, assign) CGFloat anchorMargin;
|
||||
@end
|
||||
|
||||
/// Default for iOS 7, this reproduces the "masked" behavior of the iOS 7-style callout view.
|
||||
/// Accessories are masked by the shape of the callout (including the arrow itself).
|
||||
@interface SMCalloutMaskedBackgroundView : SMCalloutBackgroundView
|
||||
@end
|
||||
|
||||
//
|
||||
// Delegate methods
|
||||
//
|
||||
|
||||
@protocol SMCalloutViewDelegate <NSObject>
|
||||
@optional
|
||||
|
||||
/// Controls whether the callout "highlights" when pressed. default YES. You must also respond to @c -calloutViewClicked below.
|
||||
/// Not honored by @c SMClassicCalloutView.
|
||||
- (BOOL)calloutViewShouldHighlight:(SMCalloutView *)calloutView;
|
||||
|
||||
/// Called when the callout view is clicked. Not honored by @c SMClassicCalloutView.
|
||||
- (void)calloutViewClicked:(SMCalloutView *)calloutView;
|
||||
|
||||
/**
|
||||
Called when the callout view detects that it will be outside the constrained view when it appears,
|
||||
or if the target rect was already outside the constrained view. You can implement this selector
|
||||
to respond to this situation by repositioning your content first in order to make everything visible.
|
||||
The @c CGSize passed is the calculated offset necessary to make everything visible (plus a nice margin).
|
||||
It expects you to return the amount of time you need to reposition things so the popup can be delayed.
|
||||
Typically you would return @c kSMCalloutViewRepositionDelayForUIScrollView if you're repositioning by calling @c [UIScrollView @c setContentOffset:animated:].
|
||||
|
||||
@param calloutView the @c SMCalloutView to reposition
|
||||
@param offset caluclated offset necessary to make everything visible
|
||||
@returns @c NSTimeInterval to delay the repositioning
|
||||
*/
|
||||
- (NSTimeInterval)calloutView:(SMCalloutView *)calloutView delayForRepositionWithSize:(CGSize)offset;
|
||||
|
||||
/// Called before the callout view appears on screen, or before the appearance animation will start.
|
||||
- (void)calloutViewWillAppear:(SMCalloutView *)calloutView;
|
||||
|
||||
/// Called after the callout view appears on screen, or after the appearance animation is complete.
|
||||
- (void)calloutViewDidAppear:(SMCalloutView *)calloutView;
|
||||
|
||||
/// Called before the callout view is removed from the screen, or before the disappearance animation is complete.
|
||||
- (void)calloutViewWillDisappear:(SMCalloutView *)calloutView;
|
||||
|
||||
/// Called after the callout view is removed from the screen, or after the disappearance animation is complete.
|
||||
- (void)calloutViewDidDisappear:(SMCalloutView *)calloutView;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@end
|
||||
859
Pods/SMCalloutView/SMCalloutView.m
generated
Executable file
859
Pods/SMCalloutView/SMCalloutView.m
generated
Executable file
@ -0,0 +1,859 @@
|
||||
#import "SMCalloutView.h"
|
||||
|
||||
//
|
||||
// UIView frame helpers - we do a lot of UIView frame fiddling in this class; these functions help keep things readable.
|
||||
//
|
||||
|
||||
@interface UIView (SMFrameAdditions)
|
||||
@property (nonatomic, assign) CGPoint frameOrigin;
|
||||
@property (nonatomic, assign) CGSize frameSize;
|
||||
@property (nonatomic, assign) CGFloat frameX, frameY, frameWidth, frameHeight; // normal rect properties
|
||||
@property (nonatomic, assign) CGFloat frameLeft, frameTop, frameRight, frameBottom; // these will stretch/shrink the rect
|
||||
@end
|
||||
|
||||
//
|
||||
// Callout View.
|
||||
//
|
||||
|
||||
#define CALLOUT_DEFAULT_CONTAINER_HEIGHT 44 // height of just the main portion without arrow
|
||||
#define CALLOUT_SUB_DEFAULT_CONTAINER_HEIGHT 52 // height of just the main portion without arrow (when subtitle is present)
|
||||
#define CALLOUT_MIN_WIDTH 61 // minimum width of system callout
|
||||
#define TITLE_HMARGIN 12 // the title/subtitle view's normal horizontal margin from the edges of our callout view or from the accessories
|
||||
#define TITLE_TOP 11 // the top of the title view when no subtitle is present
|
||||
#define TITLE_SUB_TOP 4 // the top of the title view when a subtitle IS present
|
||||
#define TITLE_HEIGHT 21 // title height, fixed
|
||||
#define SUBTITLE_TOP 28 // the top of the subtitle, when present
|
||||
#define SUBTITLE_HEIGHT 15 // subtitle height, fixed
|
||||
#define BETWEEN_ACCESSORIES_MARGIN 7 // margin between accessories when no title/subtitle is present
|
||||
#define TOP_ANCHOR_MARGIN 13 // all the above measurements assume a bottom anchor! if we're pointing "up" we'll need to add this top margin to everything.
|
||||
#define COMFORTABLE_MARGIN 10 // when we try to reposition content to be visible, we'll consider this margin around your target rect
|
||||
|
||||
NSTimeInterval const kSMCalloutViewRepositionDelayForUIScrollView = 1.0/3.0;
|
||||
|
||||
@interface SMCalloutView ()
|
||||
@property (nonatomic, strong) UIButton *containerView; // for masking and interaction
|
||||
@property (nonatomic, strong) UILabel *titleLabel, *subtitleLabel;
|
||||
@property (nonatomic, assign) SMCalloutArrowDirection currentArrowDirection;
|
||||
@property (nonatomic, assign) BOOL popupCancelled;
|
||||
@end
|
||||
|
||||
@implementation SMCalloutView
|
||||
|
||||
+ (SMCalloutView *)platformCalloutView {
|
||||
|
||||
// if you haven't compiled SMClassicCalloutView into your app, then we can't possibly create an instance of it!
|
||||
if (!NSClassFromString(@"SMClassicCalloutView"))
|
||||
return [SMCalloutView new];
|
||||
|
||||
// ok we have both - so choose the best one based on current platform
|
||||
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1)
|
||||
return [SMCalloutView new]; // iOS 7+
|
||||
else
|
||||
return [NSClassFromString(@"SMClassicCalloutView") new];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.permittedArrowDirection = SMCalloutArrowDirectionDown;
|
||||
self.presentAnimation = SMCalloutAnimationBounce;
|
||||
self.dismissAnimation = SMCalloutAnimationFade;
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.containerView = [UIButton new];
|
||||
self.containerView.isAccessibilityElement = NO;
|
||||
self.isAccessibilityElement = NO;
|
||||
self.contentViewInset = UIEdgeInsetsMake(12, 12, 12, 12);
|
||||
|
||||
[self.containerView addTarget:self action:@selector(highlightIfNecessary) forControlEvents:UIControlEventTouchDown | UIControlEventTouchDragInside];
|
||||
[self.containerView addTarget:self action:@selector(unhighlightIfNecessary) forControlEvents:UIControlEventTouchDragOutside | UIControlEventTouchCancel | UIControlEventTouchUpOutside | UIControlEventTouchUpInside];
|
||||
[self.containerView addTarget:self action:@selector(calloutClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)supportsHighlighting {
|
||||
if (![self.delegate respondsToSelector:@selector(calloutViewClicked:)])
|
||||
return NO;
|
||||
if ([self.delegate respondsToSelector:@selector(calloutViewShouldHighlight:)])
|
||||
return [self.delegate calloutViewShouldHighlight:self];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)highlightIfNecessary { if (self.supportsHighlighting) self.backgroundView.highlighted = YES; }
|
||||
- (void)unhighlightIfNecessary { if (self.supportsHighlighting) self.backgroundView.highlighted = NO; }
|
||||
|
||||
- (void)calloutClicked {
|
||||
if ([self.delegate respondsToSelector:@selector(calloutViewClicked:)])
|
||||
[self.delegate calloutViewClicked:self];
|
||||
}
|
||||
|
||||
- (UIView *)titleViewOrDefault {
|
||||
if (self.titleView)
|
||||
// if you have a custom title view defined, return that.
|
||||
return self.titleView;
|
||||
else {
|
||||
if (!self.titleLabel) {
|
||||
// create a default titleView
|
||||
self.titleLabel = [UILabel new];
|
||||
self.titleLabel.frameHeight = TITLE_HEIGHT;
|
||||
self.titleLabel.opaque = NO;
|
||||
self.titleLabel.backgroundColor = [UIColor clearColor];
|
||||
self.titleLabel.font = [UIFont systemFontOfSize:17];
|
||||
self.titleLabel.textColor = [UIColor blackColor];
|
||||
}
|
||||
return self.titleLabel;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIView *)subtitleViewOrDefault {
|
||||
if (self.subtitleView)
|
||||
// if you have a custom subtitle view defined, return that.
|
||||
return self.subtitleView;
|
||||
else {
|
||||
if (!self.subtitleLabel) {
|
||||
// create a default subtitleView
|
||||
self.subtitleLabel = [UILabel new];
|
||||
self.subtitleLabel.frameHeight = SUBTITLE_HEIGHT;
|
||||
self.subtitleLabel.opaque = NO;
|
||||
self.subtitleLabel.backgroundColor = [UIColor clearColor];
|
||||
self.subtitleLabel.font = [UIFont systemFontOfSize:12];
|
||||
self.subtitleLabel.textColor = [UIColor blackColor];
|
||||
}
|
||||
return self.subtitleLabel;
|
||||
}
|
||||
}
|
||||
|
||||
- (SMCalloutBackgroundView *)backgroundView {
|
||||
// create our default background on first access only if it's nil, since you might have set your own background anyway.
|
||||
return _backgroundView ? _backgroundView : (_backgroundView = [self defaultBackgroundView]);
|
||||
}
|
||||
|
||||
- (SMCalloutBackgroundView *)defaultBackgroundView {
|
||||
return [SMCalloutMaskedBackgroundView new];
|
||||
}
|
||||
|
||||
- (void)rebuildSubviews {
|
||||
// remove and re-add our appropriate subviews in the appropriate order
|
||||
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
[self.containerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
[self setNeedsDisplay];
|
||||
|
||||
[self addSubview:self.backgroundView];
|
||||
[self addSubview:self.containerView];
|
||||
|
||||
if (self.contentView) {
|
||||
[self.containerView addSubview:self.contentView];
|
||||
}
|
||||
else {
|
||||
if (self.titleViewOrDefault) [self.containerView addSubview:self.titleViewOrDefault];
|
||||
if (self.subtitleViewOrDefault) [self.containerView addSubview:self.subtitleViewOrDefault];
|
||||
}
|
||||
if (self.leftAccessoryView) [self.containerView addSubview:self.leftAccessoryView];
|
||||
if (self.rightAccessoryView) [self.containerView addSubview:self.rightAccessoryView];
|
||||
}
|
||||
|
||||
// Accessory margins. Accessories are centered vertically when shorter
|
||||
// than the callout, otherwise they grow from the upper corner.
|
||||
|
||||
- (CGFloat)leftAccessoryVerticalMargin {
|
||||
if (self.leftAccessoryView.frameHeight < self.calloutContainerHeight)
|
||||
return roundf((self.calloutContainerHeight - self.leftAccessoryView.frameHeight) / 2);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (CGFloat)leftAccessoryHorizontalMargin {
|
||||
return fminf(self.leftAccessoryVerticalMargin, TITLE_HMARGIN);
|
||||
}
|
||||
|
||||
- (CGFloat)rightAccessoryVerticalMargin {
|
||||
if (self.rightAccessoryView.frameHeight < self.calloutContainerHeight)
|
||||
return roundf((self.calloutContainerHeight - self.rightAccessoryView.frameHeight) / 2);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (CGFloat)rightAccessoryHorizontalMargin {
|
||||
return fminf(self.rightAccessoryVerticalMargin, TITLE_HMARGIN);
|
||||
}
|
||||
|
||||
- (CGFloat)innerContentMarginLeft {
|
||||
if (self.leftAccessoryView)
|
||||
return self.leftAccessoryHorizontalMargin + self.leftAccessoryView.frameWidth + TITLE_HMARGIN;
|
||||
else
|
||||
return self.contentViewInset.left;
|
||||
}
|
||||
|
||||
- (CGFloat)innerContentMarginRight {
|
||||
if (self.rightAccessoryView)
|
||||
return self.rightAccessoryHorizontalMargin + self.rightAccessoryView.frameWidth + TITLE_HMARGIN;
|
||||
else
|
||||
return self.contentViewInset.right;
|
||||
}
|
||||
|
||||
- (CGFloat)calloutHeight {
|
||||
return self.calloutContainerHeight + self.backgroundView.anchorHeight;
|
||||
}
|
||||
|
||||
- (CGFloat)calloutContainerHeight {
|
||||
if (self.contentView)
|
||||
return self.contentView.frameHeight + self.contentViewInset.bottom + self.contentViewInset.top;
|
||||
else if (self.subtitleView || self.subtitle.length > 0)
|
||||
return CALLOUT_SUB_DEFAULT_CONTAINER_HEIGHT;
|
||||
else
|
||||
return CALLOUT_DEFAULT_CONTAINER_HEIGHT;
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
|
||||
// calculate how much non-negotiable space we need to reserve for margin and accessories
|
||||
CGFloat margin = self.innerContentMarginLeft + self.innerContentMarginRight;
|
||||
|
||||
// how much room is left for text?
|
||||
CGFloat availableWidthForText = size.width - margin - 1;
|
||||
|
||||
// no room for text? then we'll have to squeeze into the given size somehow.
|
||||
if (availableWidthForText < 0)
|
||||
availableWidthForText = 0;
|
||||
|
||||
CGSize preferredTitleSize = [self.titleViewOrDefault sizeThatFits:CGSizeMake(availableWidthForText, TITLE_HEIGHT)];
|
||||
CGSize preferredSubtitleSize = [self.subtitleViewOrDefault sizeThatFits:CGSizeMake(availableWidthForText, SUBTITLE_HEIGHT)];
|
||||
|
||||
// total width we'd like
|
||||
CGFloat preferredWidth;
|
||||
|
||||
if (self.contentView) {
|
||||
|
||||
// if we have a content view, then take our preferred size directly from that
|
||||
preferredWidth = self.contentView.frameWidth + margin;
|
||||
}
|
||||
else if (preferredTitleSize.width >= 0.000001 || preferredSubtitleSize.width >= 0.000001) {
|
||||
|
||||
// if we have a title or subtitle, then our assumed margins are valid, and we can apply them
|
||||
preferredWidth = fmaxf(preferredTitleSize.width, preferredSubtitleSize.width) + margin;
|
||||
}
|
||||
else {
|
||||
// ok we have no title or subtitle to speak of. In this case, the system callout would actually not display
|
||||
// at all! But we can handle it.
|
||||
preferredWidth = self.leftAccessoryView.frameWidth + self.rightAccessoryView.frameWidth + self.leftAccessoryHorizontalMargin + self.rightAccessoryHorizontalMargin;
|
||||
|
||||
if (self.leftAccessoryView && self.rightAccessoryView)
|
||||
preferredWidth += BETWEEN_ACCESSORIES_MARGIN;
|
||||
}
|
||||
|
||||
// ensure we're big enough to fit our graphics!
|
||||
preferredWidth = fmaxf(preferredWidth, CALLOUT_MIN_WIDTH);
|
||||
|
||||
// ask to be smaller if we have space, otherwise we'll fit into what we have by truncating the title/subtitle.
|
||||
return CGSizeMake(fminf(preferredWidth, size.width), self.calloutHeight);
|
||||
}
|
||||
|
||||
- (CGSize)offsetToContainRect:(CGRect)innerRect inRect:(CGRect)outerRect {
|
||||
CGFloat nudgeRight = fmaxf(0, CGRectGetMinX(outerRect) - CGRectGetMinX(innerRect));
|
||||
CGFloat nudgeLeft = fminf(0, CGRectGetMaxX(outerRect) - CGRectGetMaxX(innerRect));
|
||||
CGFloat nudgeTop = fmaxf(0, CGRectGetMinY(outerRect) - CGRectGetMinY(innerRect));
|
||||
CGFloat nudgeBottom = fminf(0, CGRectGetMaxY(outerRect) - CGRectGetMaxY(innerRect));
|
||||
return CGSizeMake(nudgeLeft ? nudgeLeft : nudgeRight, nudgeTop ? nudgeTop : nudgeBottom);
|
||||
}
|
||||
|
||||
- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated {
|
||||
[self presentCalloutFromRect:rect inLayer:view.layer ofView:view constrainedToLayer:constrainedView.layer animated:animated];
|
||||
}
|
||||
|
||||
- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer constrainedToLayer:(CALayer *)constrainedLayer animated:(BOOL)animated {
|
||||
[self presentCalloutFromRect:rect inLayer:layer ofView:nil constrainedToLayer:constrainedLayer animated:animated];
|
||||
}
|
||||
|
||||
// this private method handles both CALayer and UIView parents depending on what's passed.
|
||||
- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer ofView:(UIView *)view constrainedToLayer:(CALayer *)constrainedLayer animated:(BOOL)animated {
|
||||
|
||||
// Sanity check: dismiss this callout immediately if it's displayed somewhere
|
||||
if (self.layer.superlayer) [self dismissCalloutAnimated:NO];
|
||||
|
||||
// cancel all animations that may be in progress
|
||||
[self.layer removeAnimationForKey:@"present"];
|
||||
[self.layer removeAnimationForKey:@"dismiss"];
|
||||
|
||||
// figure out the constrained view's rect in our popup view's coordinate system
|
||||
CGRect constrainedRect = [constrainedLayer convertRect:constrainedLayer.bounds toLayer:layer];
|
||||
|
||||
// apply our edge constraints
|
||||
constrainedRect = UIEdgeInsetsInsetRect(constrainedRect, self.constrainedInsets);
|
||||
|
||||
constrainedRect = CGRectInset(constrainedRect, COMFORTABLE_MARGIN, COMFORTABLE_MARGIN);
|
||||
|
||||
// form our subviews based on our content set so far
|
||||
[self rebuildSubviews];
|
||||
|
||||
// apply title/subtitle (if present
|
||||
self.titleLabel.text = self.title;
|
||||
self.subtitleLabel.text = self.subtitle;
|
||||
|
||||
// size the callout to fit the width constraint as best as possible
|
||||
self.frameSize = [self sizeThatFits:CGSizeMake(constrainedRect.size.width, self.calloutHeight)];
|
||||
|
||||
// how much room do we have in the constraint box, both above and below our target rect?
|
||||
CGFloat topSpace = CGRectGetMinY(rect) - CGRectGetMinY(constrainedRect);
|
||||
CGFloat bottomSpace = CGRectGetMaxY(constrainedRect) - CGRectGetMaxY(rect);
|
||||
|
||||
// we prefer to point our arrow down.
|
||||
SMCalloutArrowDirection bestDirection = SMCalloutArrowDirectionDown;
|
||||
|
||||
// we'll point it up though if that's the only option you gave us.
|
||||
if (self.permittedArrowDirection == SMCalloutArrowDirectionUp)
|
||||
bestDirection = SMCalloutArrowDirectionUp;
|
||||
|
||||
// or, if we don't have enough space on the top and have more space on the bottom, and you
|
||||
// gave us a choice, then pointing up is the better option.
|
||||
if (self.permittedArrowDirection == SMCalloutArrowDirectionAny && topSpace < self.calloutHeight && bottomSpace > topSpace)
|
||||
bestDirection = SMCalloutArrowDirectionUp;
|
||||
|
||||
self.currentArrowDirection = bestDirection;
|
||||
|
||||
// we want to point directly at the horizontal center of the given rect. calculate our "anchor point" in terms of our
|
||||
// target view's coordinate system. make sure to offset the anchor point as requested if necessary.
|
||||
CGFloat anchorX = self.calloutOffset.x + CGRectGetMidX(rect);
|
||||
CGFloat anchorY = self.calloutOffset.y + (bestDirection == SMCalloutArrowDirectionDown ? CGRectGetMinY(rect) : CGRectGetMaxY(rect));
|
||||
|
||||
// we prefer to sit centered directly above our anchor
|
||||
CGFloat calloutX = roundf(anchorX - self.frameWidth / 2);
|
||||
|
||||
// but not if it's going to get too close to the edge of our constraints
|
||||
if (calloutX < constrainedRect.origin.x)
|
||||
calloutX = constrainedRect.origin.x;
|
||||
|
||||
if (calloutX > constrainedRect.origin.x+constrainedRect.size.width-self.frameWidth)
|
||||
calloutX = constrainedRect.origin.x+constrainedRect.size.width-self.frameWidth;
|
||||
|
||||
// what's the farthest to the left and right that we could point to, given our background image constraints?
|
||||
CGFloat minPointX = calloutX + self.backgroundView.anchorMargin;
|
||||
CGFloat maxPointX = calloutX + self.frameWidth - self.backgroundView.anchorMargin;
|
||||
|
||||
// we may need to scoot over to the left or right to point at the correct spot
|
||||
CGFloat adjustX = 0;
|
||||
if (anchorX < minPointX) adjustX = anchorX - minPointX;
|
||||
if (anchorX > maxPointX) adjustX = anchorX - maxPointX;
|
||||
|
||||
// add the callout to the given layer (or view if possible, to receive touch events)
|
||||
if (view)
|
||||
[view addSubview:self];
|
||||
else
|
||||
[layer addSublayer:self.layer];
|
||||
|
||||
CGPoint calloutOrigin = {
|
||||
.x = calloutX + adjustX,
|
||||
.y = bestDirection == SMCalloutArrowDirectionDown ? (anchorY - self.calloutHeight) : anchorY
|
||||
};
|
||||
|
||||
self.frameOrigin = calloutOrigin;
|
||||
|
||||
// now set the *actual* anchor point for our layer so that our "popup" animation starts from this point.
|
||||
CGPoint anchorPoint = [layer convertPoint:CGPointMake(anchorX, anchorY) toLayer:self.layer];
|
||||
|
||||
// pass on the anchor point to our background view so it knows where to draw the arrow
|
||||
self.backgroundView.arrowPoint = anchorPoint;
|
||||
|
||||
// adjust it to unit coordinates for the actual layer.anchorPoint property
|
||||
anchorPoint.x /= self.frameWidth;
|
||||
anchorPoint.y /= self.frameHeight;
|
||||
self.layer.anchorPoint = anchorPoint;
|
||||
|
||||
// setting the anchor point moves the view a bit, so we need to reset
|
||||
self.frameOrigin = calloutOrigin;
|
||||
|
||||
// make sure our frame is not on half-pixels or else we may be blurry!
|
||||
CGFloat scale = [UIScreen mainScreen].scale;
|
||||
self.frameX = floorf(self.frameX*scale)/scale;
|
||||
self.frameY = floorf(self.frameY*scale)/scale;
|
||||
|
||||
// layout now so we can immediately start animating to the final position if needed
|
||||
[self setNeedsLayout];
|
||||
[self layoutIfNeeded];
|
||||
|
||||
// if we're outside the bounds of our constraint rect, we'll give our delegate an opportunity to shift us into position.
|
||||
// consider both our size and the size of our target rect (which we'll assume to be the size of the content you want to scroll into view.
|
||||
CGRect contentRect = CGRectUnion(self.frame, rect);
|
||||
CGSize offset = [self offsetToContainRect:contentRect inRect:constrainedRect];
|
||||
|
||||
NSTimeInterval delay = 0;
|
||||
self.popupCancelled = NO; // reset this before calling our delegate below
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(calloutView:delayForRepositionWithSize:)] && !CGSizeEqualToSize(offset, CGSizeZero))
|
||||
delay = [self.delegate calloutView:(id)self delayForRepositionWithSize:offset];
|
||||
|
||||
// there's a chance that user code in the delegate method may have called -dismissCalloutAnimated to cancel things; if that
|
||||
// happened then we need to bail!
|
||||
if (self.popupCancelled) return;
|
||||
|
||||
// now we want to mask our contents to our background view (if requested) to match the iOS 7 style
|
||||
self.containerView.layer.mask = self.backgroundView.contentMask;
|
||||
|
||||
// if we need to delay, we don't want to be visible while we're delaying, so hide us in preparation for our popup
|
||||
self.hidden = YES;
|
||||
|
||||
// create the appropriate animation, even if we're not animated
|
||||
CAAnimation *animation = [self animationWithType:self.presentAnimation presenting:YES];
|
||||
|
||||
// nuke the duration if no animation requested - we'll still need to "run" the animation to get delays and callbacks
|
||||
if (!animated)
|
||||
animation.duration = 0.0000001; // can't be zero or the animation won't "run"
|
||||
|
||||
animation.beginTime = CACurrentMediaTime() + delay;
|
||||
animation.delegate = self;
|
||||
|
||||
[self.layer addAnimation:animation forKey:@"present"];
|
||||
}
|
||||
|
||||
- (void)animationDidStart:(CAAnimation *)anim {
|
||||
BOOL presenting = [[anim valueForKey:@"presenting"] boolValue];
|
||||
|
||||
if (presenting) {
|
||||
if ([_delegate respondsToSelector:@selector(calloutViewWillAppear:)])
|
||||
[_delegate calloutViewWillAppear:(id)self];
|
||||
|
||||
// ok, animation is on, let's make ourselves visible!
|
||||
self.hidden = NO;
|
||||
}
|
||||
else if (!presenting) {
|
||||
if ([_delegate respondsToSelector:@selector(calloutViewWillDisappear:)])
|
||||
[_delegate calloutViewWillDisappear:(id)self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)finished {
|
||||
BOOL presenting = [[anim valueForKey:@"presenting"] boolValue];
|
||||
|
||||
if (presenting && finished) {
|
||||
if ([_delegate respondsToSelector:@selector(calloutViewDidAppear:)])
|
||||
[_delegate calloutViewDidAppear:(id)self];
|
||||
}
|
||||
else if (!presenting && finished) {
|
||||
|
||||
[self removeFromParent];
|
||||
[self.layer removeAnimationForKey:@"dismiss"];
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(calloutViewDidDisappear:)])
|
||||
[_delegate calloutViewDidDisappear:(id)self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dismissCalloutAnimated:(BOOL)animated {
|
||||
|
||||
// cancel all animations that may be in progress
|
||||
[self.layer removeAnimationForKey:@"present"];
|
||||
[self.layer removeAnimationForKey:@"dismiss"];
|
||||
|
||||
self.popupCancelled = YES;
|
||||
|
||||
if (animated) {
|
||||
CAAnimation *animation = [self animationWithType:self.dismissAnimation presenting:NO];
|
||||
animation.delegate = self;
|
||||
[self.layer addAnimation:animation forKey:@"dismiss"];
|
||||
}
|
||||
else {
|
||||
[self removeFromParent];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeFromParent {
|
||||
if (self.superview)
|
||||
[self removeFromSuperview];
|
||||
else {
|
||||
// removing a layer from a superlayer causes an implicit fade-out animation that we wish to disable.
|
||||
[CATransaction begin];
|
||||
[CATransaction setDisableActions:YES];
|
||||
[self.layer removeFromSuperlayer];
|
||||
[CATransaction commit];
|
||||
}
|
||||
}
|
||||
|
||||
- (CAAnimation *)animationWithType:(SMCalloutAnimation)type presenting:(BOOL)presenting {
|
||||
CAAnimation *animation = nil;
|
||||
|
||||
if (type == SMCalloutAnimationBounce) {
|
||||
|
||||
CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:@"opacity"];
|
||||
fade.duration = 0.23;
|
||||
fade.fromValue = presenting ? @0.0 : @1.0;
|
||||
fade.toValue = presenting ? @1.0 : @0.0;
|
||||
fade.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
||||
|
||||
CABasicAnimation *bounce = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
|
||||
bounce.duration = 0.23;
|
||||
bounce.fromValue = presenting ? @0.7 : @1.0;
|
||||
bounce.toValue = presenting ? @1.0 : @0.7;
|
||||
bounce.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.59367:0.12066:0.18878:1.5814];
|
||||
|
||||
CAAnimationGroup *group = [CAAnimationGroup animation];
|
||||
group.animations = @[fade, bounce];
|
||||
group.duration = 0.23;
|
||||
|
||||
animation = group;
|
||||
}
|
||||
else if (type == SMCalloutAnimationFade) {
|
||||
CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:@"opacity"];
|
||||
fade.duration = 1.0/3.0;
|
||||
fade.fromValue = presenting ? @0.0 : @1.0;
|
||||
fade.toValue = presenting ? @1.0 : @0.0;
|
||||
animation = fade;
|
||||
}
|
||||
else if (type == SMCalloutAnimationStretch) {
|
||||
CABasicAnimation *stretch = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
|
||||
stretch.duration = 0.1;
|
||||
stretch.fromValue = presenting ? @0.0 : @1.0;
|
||||
stretch.toValue = presenting ? @1.0 : @0.0;
|
||||
animation = stretch;
|
||||
}
|
||||
|
||||
// CAAnimation is KVC compliant, so we can store whether we're presenting for lookup in our delegate methods
|
||||
[animation setValue:@(presenting) forKey:@"presenting"];
|
||||
|
||||
animation.fillMode = kCAFillModeForwards;
|
||||
animation.removedOnCompletion = NO;
|
||||
return animation;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
|
||||
self.containerView.frame = self.bounds;
|
||||
self.backgroundView.frame = self.bounds;
|
||||
|
||||
// if we're pointing up, we'll need to push almost everything down a bit
|
||||
CGFloat dy = self.currentArrowDirection == SMCalloutArrowDirectionUp ? TOP_ANCHOR_MARGIN : 0;
|
||||
|
||||
self.titleViewOrDefault.frameX = self.innerContentMarginLeft;
|
||||
self.titleViewOrDefault.frameY = (self.subtitleView || self.subtitle.length ? TITLE_SUB_TOP : TITLE_TOP) + dy;
|
||||
self.titleViewOrDefault.frameWidth = self.frameWidth - self.innerContentMarginLeft - self.innerContentMarginRight;
|
||||
|
||||
self.subtitleViewOrDefault.frameX = self.titleViewOrDefault.frameX;
|
||||
self.subtitleViewOrDefault.frameY = SUBTITLE_TOP + dy;
|
||||
self.subtitleViewOrDefault.frameWidth = self.titleViewOrDefault.frameWidth;
|
||||
|
||||
self.leftAccessoryView.frameX = self.leftAccessoryHorizontalMargin;
|
||||
self.leftAccessoryView.frameY = self.leftAccessoryVerticalMargin + dy;
|
||||
|
||||
self.rightAccessoryView.frameX = self.frameWidth - self.rightAccessoryHorizontalMargin - self.rightAccessoryView.frameWidth;
|
||||
self.rightAccessoryView.frameY = self.rightAccessoryVerticalMargin + dy;
|
||||
|
||||
if (self.contentView) {
|
||||
self.contentView.frameX = self.innerContentMarginLeft;
|
||||
self.contentView.frameY = self.contentViewInset.top + dy;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Accessibility
|
||||
|
||||
- (NSInteger)accessibilityElementCount {
|
||||
return (!!self.leftAccessoryView + !!self.titleViewOrDefault +
|
||||
!!self.subtitleViewOrDefault + !!self.rightAccessoryView);
|
||||
}
|
||||
|
||||
- (id)accessibilityElementAtIndex:(NSInteger)index {
|
||||
if (index == 0) {
|
||||
return self.leftAccessoryView ? self.leftAccessoryView : self.titleViewOrDefault;
|
||||
}
|
||||
if (index == 1) {
|
||||
return self.leftAccessoryView ? self.titleViewOrDefault : self.subtitleViewOrDefault;
|
||||
}
|
||||
if (index == 2) {
|
||||
return self.leftAccessoryView ? self.subtitleViewOrDefault : self.rightAccessoryView;
|
||||
}
|
||||
if (index == 3) {
|
||||
return self.leftAccessoryView ? self.rightAccessoryView : nil;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSInteger)indexOfAccessibilityElement:(id)element {
|
||||
if (element == nil) return NSNotFound;
|
||||
if (element == self.leftAccessoryView) return 0;
|
||||
if (element == self.titleViewOrDefault) {
|
||||
return self.leftAccessoryView ? 1 : 0;
|
||||
}
|
||||
if (element == self.subtitleViewOrDefault) {
|
||||
return self.leftAccessoryView ? 2 : 1;
|
||||
}
|
||||
if (element == self.rightAccessoryView) {
|
||||
return self.leftAccessoryView ? 3 : 2;
|
||||
}
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// import this known "private API" from SMCalloutBackgroundView
|
||||
@interface SMCalloutBackgroundView (EmbeddedImages)
|
||||
+ (UIImage *)embeddedImageNamed:(NSString *)name;
|
||||
@end
|
||||
|
||||
//
|
||||
// Callout Background View.
|
||||
//
|
||||
|
||||
@interface SMCalloutMaskedBackgroundView ()
|
||||
@property (nonatomic, strong) UIView *containerView, *containerBorderView, *arrowView;
|
||||
@property (nonatomic, strong) UIImageView *arrowImageView, *arrowHighlightedImageView, *arrowBorderView;
|
||||
@end
|
||||
|
||||
static UIImage *blackArrowImage = nil, *whiteArrowImage = nil, *grayArrowImage = nil;
|
||||
|
||||
@implementation SMCalloutMaskedBackgroundView
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
|
||||
// Here we're mimicking the very particular (and odd) structure of the system callout view.
|
||||
// The hierarchy and view/layer values were discovered by inspecting map kit using Reveal.app
|
||||
|
||||
self.containerView = [UIView new];
|
||||
self.containerView.backgroundColor = [UIColor whiteColor];
|
||||
self.containerView.alpha = 0.96;
|
||||
self.containerView.layer.cornerRadius = 8;
|
||||
self.containerView.layer.shadowRadius = 30;
|
||||
self.containerView.layer.shadowOpacity = 0.1;
|
||||
|
||||
self.containerBorderView = [UIView new];
|
||||
self.containerBorderView.layer.borderColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor;
|
||||
self.containerBorderView.layer.borderWidth = 0.5;
|
||||
self.containerBorderView.layer.cornerRadius = 8.5;
|
||||
|
||||
if (!blackArrowImage) {
|
||||
blackArrowImage = [SMCalloutBackgroundView embeddedImageNamed:@"CalloutArrow"];
|
||||
whiteArrowImage = [self image:blackArrowImage withColor:[UIColor whiteColor]];
|
||||
grayArrowImage = [self image:blackArrowImage withColor:[UIColor colorWithWhite:0.85 alpha:1]];
|
||||
}
|
||||
|
||||
self.anchorHeight = 13;
|
||||
self.anchorMargin = 27;
|
||||
|
||||
self.arrowView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, blackArrowImage.size.width, blackArrowImage.size.height)];
|
||||
self.arrowView.alpha = 0.96;
|
||||
self.arrowImageView = [[UIImageView alloc] initWithImage:whiteArrowImage];
|
||||
self.arrowHighlightedImageView = [[UIImageView alloc] initWithImage:grayArrowImage];
|
||||
self.arrowHighlightedImageView.hidden = YES;
|
||||
self.arrowBorderView = [[UIImageView alloc] initWithImage:blackArrowImage];
|
||||
self.arrowBorderView.alpha = 0.1;
|
||||
self.arrowBorderView.frameY = 0.5;
|
||||
|
||||
[self addSubview:self.containerView];
|
||||
[self.containerView addSubview:self.containerBorderView];
|
||||
[self addSubview:self.arrowView];
|
||||
[self.arrowView addSubview:self.arrowBorderView];
|
||||
[self.arrowView addSubview:self.arrowImageView];
|
||||
[self.arrowView addSubview:self.arrowHighlightedImageView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// Make sure we relayout our images when our arrow point changes!
|
||||
- (void)setArrowPoint:(CGPoint)arrowPoint {
|
||||
[super setArrowPoint:arrowPoint];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setHighlighted:(BOOL)highlighted {
|
||||
[super setHighlighted:highlighted];
|
||||
self.containerView.backgroundColor = highlighted ? [UIColor colorWithWhite:0.85 alpha:1] : [UIColor whiteColor];
|
||||
self.arrowImageView.hidden = highlighted;
|
||||
self.arrowHighlightedImageView.hidden = !highlighted;
|
||||
}
|
||||
|
||||
- (UIImage *)image:(UIImage *)image withColor:(UIColor *)color {
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
|
||||
CGRect imageRect = (CGRect){.size=image.size};
|
||||
CGContextRef c = UIGraphicsGetCurrentContext();
|
||||
CGContextTranslateCTM(c, 0, image.size.height);
|
||||
CGContextScaleCTM(c, 1, -1);
|
||||
CGContextClipToMask(c, imageRect, image.CGImage);
|
||||
[color setFill];
|
||||
CGContextFillRect(c, imageRect);
|
||||
UIImage *whiteImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return whiteImage;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
|
||||
BOOL pointingUp = self.arrowPoint.y < self.frameHeight/2;
|
||||
|
||||
// if we're pointing up, we'll need to push almost everything down a bit
|
||||
CGFloat dy = pointingUp ? TOP_ANCHOR_MARGIN : 0;
|
||||
|
||||
self.containerView.frame = CGRectMake(0, dy, self.frameWidth, self.frameHeight - self.arrowView.frameHeight + 0.5);
|
||||
self.containerBorderView.frame = CGRectInset(self.containerView.bounds, -0.5, -0.5);
|
||||
|
||||
self.arrowView.frameX = roundf(self.arrowPoint.x - self.arrowView.frameWidth / 2);
|
||||
|
||||
if (pointingUp) {
|
||||
self.arrowView.frameY = 1;
|
||||
self.arrowView.transform = CGAffineTransformMakeRotation(M_PI);
|
||||
}
|
||||
else {
|
||||
self.arrowView.frameY = self.containerView.frameHeight - 0.5;
|
||||
self.arrowView.transform = CGAffineTransformIdentity;
|
||||
}
|
||||
}
|
||||
|
||||
- (CALayer *)contentMask {
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
|
||||
|
||||
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
|
||||
|
||||
UIImage *maskImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
CALayer *layer = [CALayer layer];
|
||||
layer.frame = self.bounds;
|
||||
layer.contents = (id)maskImage.CGImage;
|
||||
return layer;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SMCalloutBackgroundView
|
||||
|
||||
+ (NSData *)dataWithBase64EncodedString:(NSString *)string {
|
||||
//
|
||||
// NSData+Base64.m
|
||||
//
|
||||
// Version 1.0.2
|
||||
//
|
||||
// Created by Nick Lockwood on 12/01/2012.
|
||||
// Copyright (C) 2012 Charcoal Design
|
||||
//
|
||||
// Distributed under the permissive zlib License
|
||||
// Get the latest version from here:
|
||||
//
|
||||
// https://github.com/nicklockwood/Base64
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
const char lookup[] = {
|
||||
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 62, 99, 99, 99, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 99, 99, 99, 99, 99, 99,
|
||||
99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 99, 99, 99, 99, 99,
|
||||
99, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 99, 99, 99, 99, 99
|
||||
};
|
||||
|
||||
NSData *inputData = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
|
||||
long long inputLength = [inputData length];
|
||||
const unsigned char *inputBytes = [inputData bytes];
|
||||
|
||||
long long maxOutputLength = (inputLength / 4 + 1) * 3;
|
||||
NSMutableData *outputData = [NSMutableData dataWithLength:(NSUInteger)maxOutputLength];
|
||||
unsigned char *outputBytes = (unsigned char *)[outputData mutableBytes];
|
||||
|
||||
int accumulator = 0;
|
||||
long long outputLength = 0;
|
||||
unsigned char accumulated[] = {0, 0, 0, 0};
|
||||
for (long long i = 0; i < inputLength; i++) {
|
||||
unsigned char decoded = lookup[inputBytes[i] & 0x7F];
|
||||
if (decoded != 99) {
|
||||
accumulated[accumulator] = decoded;
|
||||
if (accumulator == 3) {
|
||||
outputBytes[outputLength++] = (accumulated[0] << 2) | (accumulated[1] >> 4);
|
||||
outputBytes[outputLength++] = (accumulated[1] << 4) | (accumulated[2] >> 2);
|
||||
outputBytes[outputLength++] = (accumulated[2] << 6) | accumulated[3];
|
||||
}
|
||||
accumulator = (accumulator + 1) % 4;
|
||||
}
|
||||
}
|
||||
|
||||
//handle left-over data
|
||||
if (accumulator > 0) outputBytes[outputLength] = (accumulated[0] << 2) | (accumulated[1] >> 4);
|
||||
if (accumulator > 1) outputBytes[++outputLength] = (accumulated[1] << 4) | (accumulated[2] >> 2);
|
||||
if (accumulator > 2) outputLength++;
|
||||
|
||||
//truncate data to match actual output length
|
||||
outputData.length = (NSUInteger)outputLength;
|
||||
return outputLength? outputData: nil;
|
||||
}
|
||||
|
||||
+ (UIImage *)embeddedImageNamed:(NSString *)name {
|
||||
CGFloat screenScale = [UIScreen mainScreen].scale;
|
||||
if (screenScale > 1.0) {
|
||||
name = [name stringByAppendingString:@"_2x"];
|
||||
screenScale = 2.0;
|
||||
}
|
||||
|
||||
SEL selector = NSSelectorFromString(name);
|
||||
|
||||
if (![(id)self respondsToSelector:selector]) {
|
||||
NSLog(@"Could not find an embedded image. Ensure that you've added a class-level method named +%@", name);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// We need to hush the compiler here - but we know what we're doing!
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
NSString *base64String = [(id)self performSelector:selector];
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
UIImage *rawImage = [UIImage imageWithData:[self dataWithBase64EncodedString:base64String]];
|
||||
return [UIImage imageWithCGImage:rawImage.CGImage scale:screenScale orientation:UIImageOrientationUp];
|
||||
}
|
||||
|
||||
+ (NSString *)CalloutArrow { return @"iVBORw0KGgoAAAANSUhEUgAAACcAAAANCAYAAAAqlHdlAAAAHGlET1QAAAACAAAAAAAAAAcAAAAoAAAABwAAAAYAAADJEgYpIwAAAJVJREFUOBFiYIAAdn5+fkFOTkE5Dg5eW05O3lJOTr6zQPyfDhhoD28pxF5BOZA7gE5ih7oLN8XJyR8MdNwrGjkQaC5/MG7biZDh4OBXBDruLpUdeBdkLhHWE1bCzs6nAnTcUyo58DnIPMK2kqAC6DALIP5JoQNB+i1IsJZ4pcBEm0iJ40D6ibeNDJVAx00k04ETSbUOAAAA//+SwicfAAAAe0lEQVRjYCAdMHNy8u7l5OT7Tzzm3Qu0hpl0q8jQwcPDIwp02B0iHXeHl5dXhAxryNfCzc2tC3TcJwIO/ARSR74tFOjk4uL1BzruHw4H/gPJU2A85Vq5uPjTgY77g+bAPyBxyk2nggkcHPxOnJz8B4AOfAGiQXwqGMsAACGK1kPPMHNBAAAAAElFTkSuQmCC"; }
|
||||
|
||||
+ (NSString *)CalloutArrow_2x { return @"iVBORw0KGgoAAAANSUhEUgAAAE4AAAAaCAYAAAAZtWr8AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAHGlET1QAAAACAAAAAAAAAA0AAAAoAAAADQAAAA0AAAFMRh0LGwAAARhJREFUWAnclbENwjAQRZ0mih2fDYgsQEVDxQZMgKjpWYAJkBANI8AGDIEoM0WkzBDRAf8klB44g0OkU1zE3/+9RIpS7VVY730/y/woTWlsjJ9iPcN9pbXfY85auyvm/qcDNmb0e2Z+sk/ZBTthN0oVttX12mJIWeaWEFf+kbySmZQa0msu3nzaGJprTXV3BVLNDG/if7bNOTeAvFP35NGJu39GL7Abb27bFXncVQBZLgJf3jp+ebSWIxZMgrxdvPJoJ4gqHpXgV36ITR46HUGaiNMKB6YQd4lI3gV8qTBjmDhrbQFxVQTyKu4ShjJQap7nE4hrfiiv4Q6B8MLGat1bQNztB/JwZm8Rli5wujFu821xfGZgLPUAAAD//4wvm4gAAAD7SURBVOWXMQ6CMBiFgaFpi6VyBEedXJy4hMQTeBSvRDgJEySegI3EQWOivkZnqUB/k0LyL7R9L++D9G+DwP0TCZGUqCdRlYgUuY9F4JCmqQa0hgBcY7wIItFZMLZYS5l0ruAZbXhs6BIROgmhcoB7OIAHTZUTRqG3wp9xmhqc0aRPQu8YAlwxIbwCEUL6GH9wfDcLXY2HpyvvmkHf9+BcrwCuHQGvNRp9Pl6OY0PPAO42AB7WqMxLKLahpFR7gLv/AA9zPe+gtvAMCIC7WMC7CqEPtrqzmBfHyy3A1V/g1Th27GYBY0BIxrk6Ap65254/VZp30GID9JwteQEZrVMWXqGn8gAAAABJRU5ErkJggg=="; }
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// Our UIView frame helpers implementation
|
||||
//
|
||||
|
||||
@implementation UIView (SMFrameAdditions)
|
||||
|
||||
- (CGPoint)frameOrigin { return self.frame.origin; }
|
||||
- (void)setFrameOrigin:(CGPoint)origin { self.frame = (CGRect){ .origin=origin, .size=self.frame.size }; }
|
||||
|
||||
- (CGFloat)frameX { return self.frame.origin.x; }
|
||||
- (void)setFrameX:(CGFloat)x { self.frame = (CGRect){ .origin.x=x, .origin.y=self.frame.origin.y, .size=self.frame.size }; }
|
||||
|
||||
- (CGFloat)frameY { return self.frame.origin.y; }
|
||||
- (void)setFrameY:(CGFloat)y { self.frame = (CGRect){ .origin.x=self.frame.origin.x, .origin.y=y, .size=self.frame.size }; }
|
||||
|
||||
- (CGSize)frameSize { return self.frame.size; }
|
||||
- (void)setFrameSize:(CGSize)size { self.frame = (CGRect){ .origin=self.frame.origin, .size=size }; }
|
||||
|
||||
- (CGFloat)frameWidth { return self.frame.size.width; }
|
||||
- (void)setFrameWidth:(CGFloat)width { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=width, .size.height=self.frame.size.height }; }
|
||||
|
||||
- (CGFloat)frameHeight { return self.frame.size.height; }
|
||||
- (void)setFrameHeight:(CGFloat)height { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=self.frame.size.width, .size.height=height }; }
|
||||
|
||||
- (CGFloat)frameLeft { return self.frame.origin.x; }
|
||||
- (void)setFrameLeft:(CGFloat)left { self.frame = (CGRect){ .origin.x=left, .origin.y=self.frame.origin.y, .size.width=fmaxf(self.frame.origin.x+self.frame.size.width-left,0), .size.height=self.frame.size.height }; }
|
||||
|
||||
- (CGFloat)frameTop { return self.frame.origin.y; }
|
||||
- (void)setFrameTop:(CGFloat)top { self.frame = (CGRect){ .origin.x=self.frame.origin.x, .origin.y=top, .size.width=self.frame.size.width, .size.height=fmaxf(self.frame.origin.y+self.frame.size.height-top,0) }; }
|
||||
|
||||
- (CGFloat)frameRight { return self.frame.origin.x + self.frame.size.width; }
|
||||
- (void)setFrameRight:(CGFloat)right { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=fmaxf(right-self.frame.origin.x,0), .size.height=self.frame.size.height }; }
|
||||
|
||||
- (CGFloat)frameBottom { return self.frame.origin.y + self.frame.size.height; }
|
||||
- (void)setFrameBottom:(CGFloat)bottom { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=self.frame.size.width, .size.height=fmaxf(bottom-self.frame.origin.y,0) }; }
|
||||
|
||||
@end
|
||||
39
Pods/SMCalloutView/SMClassicCalloutView.h
generated
Normal file
39
Pods/SMCalloutView/SMClassicCalloutView.h
generated
Normal file
@ -0,0 +1,39 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "SMCalloutView.h"
|
||||
|
||||
/*
|
||||
|
||||
SMClassicCalloutView
|
||||
--------------------
|
||||
Created by Nick Farina (nfarina@gmail.com)
|
||||
Version 1.1
|
||||
|
||||
*/
|
||||
|
||||
@protocol SMCalloutViewDelegate;
|
||||
@class SMCalloutBackgroundView;
|
||||
|
||||
//
|
||||
// Classic Callout view.
|
||||
//
|
||||
|
||||
@interface SMClassicCalloutView : SMCalloutView
|
||||
|
||||
// One thing to note about the classic callout is that it will ignore the "constrainedInsets" property. That property is designed for iOS-7
|
||||
// style presentation where your target view surface may be operlapped by navigation bars, tab bars, etc.
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// Classes responsible for drawing the background graphic with the pointy arrow.
|
||||
//
|
||||
|
||||
// Draws a background composed of stretched prerendered images that you can customize. Uses the embedded iOS 6 graphics by default.
|
||||
@interface SMCalloutImageBackgroundView : SMCalloutBackgroundView
|
||||
@property (nonatomic, strong) UIImage *leftCapImage, *rightCapImage, *topAnchorImage, *bottomAnchorImage, *backgroundImage;
|
||||
@end
|
||||
|
||||
// Draws a custom background matching the system background but can grow in height.
|
||||
@interface SMCalloutDrawnBackgroundView : SMCalloutBackgroundView
|
||||
@end
|
||||
|
||||
871
Pods/SMCalloutView/SMClassicCalloutView.m
generated
Normal file
871
Pods/SMCalloutView/SMClassicCalloutView.m
generated
Normal file
@ -0,0 +1,871 @@
|
||||
#import "SMClassicCalloutView.h"
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
//
|
||||
// UIView frame helpers - we do a lot of UIView frame fiddling in this class; these functions help keep things readable.
|
||||
//
|
||||
|
||||
@interface UIView (SMFrameAdditions)
|
||||
@property (nonatomic, assign) CGPoint frameOrigin;
|
||||
@property (nonatomic, assign) CGSize frameSize;
|
||||
@property (nonatomic, assign) CGFloat frameX, frameY, frameWidth, frameHeight; // normal rect properties
|
||||
@property (nonatomic, assign) CGFloat frameLeft, frameTop, frameRight, frameBottom; // these will stretch/shrink the rect
|
||||
@end
|
||||
|
||||
//
|
||||
// Callout View.
|
||||
//
|
||||
|
||||
#define CALLOUT_DEFAULT_MIN_WIDTH 75 // our image-based background graphics limit us to this minimum width...
|
||||
#define CALLOUT_DEFAULT_HEIGHT 70 // ...and allow only for this exact height.
|
||||
#define CALLOUT_DEFAULT_WIDTH 153 // default "I give up" width when we are asked to present in a space less than our min width
|
||||
#define TITLE_MARGIN 17 // the title view's normal horizontal margin from the edges of our callout view
|
||||
#define TITLE_TOP 11 // the top of the title view when no subtitle is present
|
||||
#define TITLE_SUB_TOP 3 // the top of the title view when a subtitle IS present
|
||||
#define TITLE_HEIGHT 22 // title height, fixed
|
||||
#define SUBTITLE_TOP 25 // the top of the subtitle, when present
|
||||
#define SUBTITLE_HEIGHT 16 // subtitle height, fixed
|
||||
#define TITLE_ACCESSORY_MARGIN 6 // the margin between the title and an accessory if one is present (on either side)
|
||||
#define ACCESSORY_MARGIN 14 // the accessory's margin from the edges of our callout view
|
||||
#define ACCESSORY_TOP 8 // the top of the accessory "area" in which accessory views are placed
|
||||
#define ACCESSORY_HEIGHT 32 // the "suggested" maximum height of an accessory view. shorter accessories will be vertically centered
|
||||
#define BETWEEN_ACCESSORIES_MARGIN 7 // if we have no title or subtitle, but have two accessory views, then this is the space between them
|
||||
#define ANCHOR_MARGIN 39 // the smallest possible distance from the edge of our control to the "tip" of the anchor, from either left or right
|
||||
#define TOP_ANCHOR_MARGIN 13 // all the above measurements assume a bottom anchor! if we're pointing "up" we'll need to add this top margin to everything.
|
||||
#define BOTTOM_ANCHOR_MARGIN 10 // if using a bottom anchor, we'll need to account for the shadow below the "tip"
|
||||
#define REPOSITION_MARGIN 10 // when we try to reposition content to be visible, we'll consider this margin around your target rect
|
||||
|
||||
#define TOP_SHADOW_BUFFER 2 // height offset buffer to account for top shadow
|
||||
#define BOTTOM_SHADOW_BUFFER 5 // height offset buffer to account for bottom shadow
|
||||
#define OFFSET_FROM_ORIGIN 5 // distance to offset vertically from the rect origin of the callout
|
||||
#define ANCHOR_HEIGHT 14 // height to use for the anchor
|
||||
#define ANCHOR_MARGIN_MIN 24 // the smallest possible distance from the edge of our control to the edge of the anchor, from either left or right
|
||||
|
||||
@interface SMCalloutView (PrivateMethods)
|
||||
@property (nonatomic, strong) UILabel *titleLabel, *subtitleLabel;
|
||||
@property (nonatomic, assign) SMCalloutArrowDirection currentArrowDirection;
|
||||
@property (nonatomic, assign) BOOL popupCancelled;
|
||||
//@property (nonatomic, strong) UIImageView *leftCap, *rightCap, *topAnchor, *bottomAnchor, *leftBackground, *rightBackground;
|
||||
@end
|
||||
|
||||
@interface SMClassicCalloutView ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation SMClassicCalloutView
|
||||
|
||||
- (UIView *)titleViewOrDefault {
|
||||
if (self.titleView)
|
||||
// if you have a custom title view defined, return that.
|
||||
return self.titleView;
|
||||
else {
|
||||
if (!self.titleLabel) {
|
||||
// create a default titleView
|
||||
self.titleLabel = [UILabel new];
|
||||
self.titleLabel.frameHeight = TITLE_HEIGHT;
|
||||
self.titleLabel.opaque = NO;
|
||||
self.titleLabel.backgroundColor = [UIColor clearColor];
|
||||
self.titleLabel.font = [UIFont boldSystemFontOfSize:17];
|
||||
self.titleLabel.textColor = [UIColor whiteColor];
|
||||
self.titleLabel.shadowColor = [UIColor colorWithWhite:0 alpha:0.5];
|
||||
self.titleLabel.shadowOffset = CGSizeMake(0, -1);
|
||||
}
|
||||
return self.titleLabel;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIView *)subtitleViewOrDefault {
|
||||
if (self.subtitleView)
|
||||
// if you have a custom subtitle view defined, return that.
|
||||
return self.subtitleView;
|
||||
else {
|
||||
if (!self.subtitleLabel) {
|
||||
// create a default subtitleView
|
||||
self.subtitleLabel = [UILabel new];
|
||||
self.subtitleLabel.frameHeight = SUBTITLE_HEIGHT;
|
||||
self.subtitleLabel.opaque = NO;
|
||||
self.subtitleLabel.backgroundColor = [UIColor clearColor];
|
||||
self.subtitleLabel.font = [UIFont systemFontOfSize:12];
|
||||
self.subtitleLabel.textColor = [UIColor whiteColor];
|
||||
self.subtitleLabel.shadowColor = [UIColor colorWithWhite:0 alpha:0.5];
|
||||
self.subtitleLabel.shadowOffset = CGSizeMake(0, -1);
|
||||
}
|
||||
return self.subtitleLabel;
|
||||
}
|
||||
}
|
||||
|
||||
- (SMCalloutBackgroundView *)defaultBackgroundView {
|
||||
return [SMCalloutDrawnBackgroundView new];
|
||||
}
|
||||
|
||||
- (void)rebuildSubviews {
|
||||
// remove and re-add our appropriate subviews in the appropriate order
|
||||
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
[self setNeedsDisplay];
|
||||
|
||||
[self addSubview:self.backgroundView];
|
||||
|
||||
if (self.contentView) {
|
||||
[self addSubview:self.contentView];
|
||||
}
|
||||
else {
|
||||
if (self.titleViewOrDefault) [self addSubview:self.titleViewOrDefault];
|
||||
if (self.subtitleViewOrDefault) [self addSubview:self.subtitleViewOrDefault];
|
||||
}
|
||||
if (self.leftAccessoryView) [self addSubview:self.leftAccessoryView];
|
||||
if (self.rightAccessoryView) [self addSubview:self.rightAccessoryView];
|
||||
}
|
||||
|
||||
- (CGFloat)innerContentMarginLeft {
|
||||
if (self.leftAccessoryView)
|
||||
return ACCESSORY_MARGIN + self.leftAccessoryView.frameWidth + TITLE_ACCESSORY_MARGIN;
|
||||
else
|
||||
return TITLE_MARGIN;
|
||||
}
|
||||
|
||||
- (CGFloat)innerContentMarginRight {
|
||||
if (self.rightAccessoryView)
|
||||
return ACCESSORY_MARGIN + self.rightAccessoryView.frameWidth + TITLE_ACCESSORY_MARGIN;
|
||||
else
|
||||
return TITLE_MARGIN;
|
||||
}
|
||||
|
||||
- (CGFloat)calloutHeight {
|
||||
if (self.contentView)
|
||||
return self.contentView.frameHeight + TITLE_TOP*2 + ANCHOR_HEIGHT + BOTTOM_ANCHOR_MARGIN;
|
||||
else
|
||||
return CALLOUT_DEFAULT_HEIGHT;
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
|
||||
// odd behavior, but mimicking the system callout view
|
||||
if (size.width < CALLOUT_DEFAULT_MIN_WIDTH)
|
||||
return CGSizeMake(CALLOUT_DEFAULT_WIDTH, self.calloutHeight);
|
||||
|
||||
// calculate how much non-negotiable space we need to reserve for margin and accessories
|
||||
CGFloat margin = self.innerContentMarginLeft + self.innerContentMarginRight;
|
||||
|
||||
// how much room is left for text?
|
||||
CGFloat availableWidthForText = size.width - margin;
|
||||
|
||||
// no room for text? then we'll have to squeeze into the given size somehow.
|
||||
if (availableWidthForText < 0)
|
||||
availableWidthForText = 0;
|
||||
|
||||
CGSize preferredTitleSize = [self.titleViewOrDefault sizeThatFits:CGSizeMake(availableWidthForText, TITLE_HEIGHT)];
|
||||
CGSize preferredSubtitleSize = [self.subtitleViewOrDefault sizeThatFits:CGSizeMake(availableWidthForText, SUBTITLE_HEIGHT)];
|
||||
|
||||
// total width we'd like
|
||||
CGFloat preferredWidth;
|
||||
|
||||
if (self.contentView) {
|
||||
|
||||
// if we have a content view, then take our preferred size directly from that
|
||||
preferredWidth = self.contentView.frameWidth + margin;
|
||||
}
|
||||
else if (preferredTitleSize.width >= 0.000001 || preferredSubtitleSize.width >= 0.000001) {
|
||||
|
||||
// if we have a title or subtitle, then our assumed margins are valid, and we can apply them
|
||||
preferredWidth = fmaxf(preferredTitleSize.width, preferredSubtitleSize.width) + margin;
|
||||
}
|
||||
else {
|
||||
// ok we have no title or subtitle to speak of. In this case, the system callout would actually not display
|
||||
// at all! But we can handle it.
|
||||
preferredWidth = self.leftAccessoryView.frameWidth + self.rightAccessoryView.frameWidth + ACCESSORY_MARGIN*2;
|
||||
|
||||
if (self.leftAccessoryView && self.rightAccessoryView)
|
||||
preferredWidth += BETWEEN_ACCESSORIES_MARGIN;
|
||||
}
|
||||
|
||||
// ensure we're big enough to fit our graphics!
|
||||
preferredWidth = fmaxf(preferredWidth, CALLOUT_DEFAULT_MIN_WIDTH);
|
||||
|
||||
// ask to be smaller if we have space, otherwise we'll fit into what we have by truncating the title/subtitle.
|
||||
return CGSizeMake(fminf(preferredWidth, size.width), self.calloutHeight);
|
||||
}
|
||||
|
||||
- (CGSize)offsetToContainRect:(CGRect)innerRect inRect:(CGRect)outerRect {
|
||||
CGFloat nudgeRight = fmaxf(0, CGRectGetMinX(outerRect) - CGRectGetMinX(innerRect));
|
||||
CGFloat nudgeLeft = fminf(0, CGRectGetMaxX(outerRect) - CGRectGetMaxX(innerRect));
|
||||
CGFloat nudgeTop = fmaxf(0, CGRectGetMinY(outerRect) - CGRectGetMinY(innerRect));
|
||||
CGFloat nudgeBottom = fminf(0, CGRectGetMaxY(outerRect) - CGRectGetMaxY(innerRect));
|
||||
return CGSizeMake(nudgeLeft ? nudgeLeft : nudgeRight, nudgeTop ? nudgeTop : nudgeBottom);
|
||||
}
|
||||
|
||||
- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated {
|
||||
[self presentCalloutFromRect:rect inLayer:view.layer ofView:view constrainedToLayer:constrainedView.layer animated:animated];
|
||||
}
|
||||
|
||||
- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer constrainedToLayer:(CALayer *)constrainedLayer animated:(BOOL)animated {
|
||||
[self presentCalloutFromRect:rect inLayer:layer ofView:nil constrainedToLayer:constrainedLayer animated:animated];
|
||||
}
|
||||
|
||||
// this private method handles both CALayer and UIView parents depending on what's passed.
|
||||
- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer ofView:(UIView *)view constrainedToLayer:(CALayer *)constrainedLayer animated:(BOOL)animated {
|
||||
|
||||
// Sanity check: dismiss this callout immediately if it's displayed somewhere
|
||||
if (self.layer.superlayer) [self dismissCalloutAnimated:NO];
|
||||
|
||||
// figure out the constrained view's rect in our popup view's coordinate system
|
||||
CGRect constrainedRect = [constrainedLayer convertRect:constrainedLayer.bounds toLayer:layer];
|
||||
|
||||
// apply our edge constraints
|
||||
constrainedRect = UIEdgeInsetsInsetRect(constrainedRect, self.constrainedInsets);
|
||||
|
||||
// form our subviews based on our content set so far
|
||||
[self rebuildSubviews];
|
||||
|
||||
// apply title/subtitle (if present
|
||||
self.titleLabel.text = self.title;
|
||||
self.subtitleLabel.text = self.subtitle;
|
||||
|
||||
// size the callout to fit the width constraint as best as possible
|
||||
self.frameSize = [self sizeThatFits:CGSizeMake(constrainedRect.size.width, self.calloutHeight)];
|
||||
|
||||
// how much room do we have in the constraint box, both above and below our target rect?
|
||||
CGFloat topSpace = CGRectGetMinY(rect) - CGRectGetMinY(constrainedRect);
|
||||
CGFloat bottomSpace = CGRectGetMaxY(constrainedRect) - CGRectGetMaxY(rect);
|
||||
|
||||
// we prefer to point our arrow down.
|
||||
SMCalloutArrowDirection bestDirection = SMCalloutArrowDirectionDown;
|
||||
|
||||
// we'll point it up though if that's the only option you gave us.
|
||||
if (self.permittedArrowDirection == SMCalloutArrowDirectionUp)
|
||||
bestDirection = SMCalloutArrowDirectionUp;
|
||||
|
||||
// or, if we don't have enough space on the top and have more space on the bottom, and you
|
||||
// gave us a choice, then pointing up is the better option.
|
||||
if (self.permittedArrowDirection == SMCalloutArrowDirectionAny && topSpace < self.calloutHeight && bottomSpace > topSpace)
|
||||
bestDirection = SMCalloutArrowDirectionUp;
|
||||
|
||||
// we want to point directly at the horizontal center of the given rect. calculate our "anchor point" in terms of our
|
||||
// target view's coordinate system. make sure to offset the anchor point as requested if necessary.
|
||||
CGFloat anchorX = self.calloutOffset.x + CGRectGetMidX(rect);
|
||||
CGFloat anchorY = self.calloutOffset.y + (bestDirection == SMCalloutArrowDirectionDown ? CGRectGetMinY(rect) : CGRectGetMaxY(rect));
|
||||
|
||||
// we prefer to sit in the exact center of our constrained view, so we have visually pleasing equal left/right margins.
|
||||
CGFloat calloutX = roundf(CGRectGetMidX(constrainedRect) - self.frameWidth / 2);
|
||||
|
||||
// what's the farthest to the left and right that we could point to, given our background image constraints?
|
||||
CGFloat minPointX = calloutX + ANCHOR_MARGIN;
|
||||
CGFloat maxPointX = calloutX + self.frameWidth - ANCHOR_MARGIN;
|
||||
|
||||
// we may need to scoot over to the left or right to point at the correct spot
|
||||
CGFloat adjustX = 0;
|
||||
if (anchorX < minPointX) adjustX = anchorX - minPointX;
|
||||
if (anchorX > maxPointX) adjustX = anchorX - maxPointX;
|
||||
|
||||
// add the callout to the given layer (or view if possible, to receive touch events)
|
||||
if (view)
|
||||
[view addSubview:self];
|
||||
else
|
||||
[layer addSublayer:self.layer];
|
||||
|
||||
CGPoint calloutOrigin = {
|
||||
.x = calloutX + adjustX,
|
||||
.y = bestDirection == SMCalloutArrowDirectionDown ? (anchorY - self.calloutHeight + BOTTOM_ANCHOR_MARGIN) : anchorY
|
||||
};
|
||||
|
||||
self.currentArrowDirection = bestDirection;
|
||||
|
||||
self.frameOrigin = calloutOrigin;
|
||||
|
||||
// now set the *actual* anchor point for our layer so that our "popup" animation starts from this point.
|
||||
CGPoint anchorPoint = [layer convertPoint:CGPointMake(anchorX, anchorY) toLayer:self.layer];
|
||||
|
||||
// pass on the anchor point to our background view so it knows where to draw the arrow
|
||||
self.backgroundView.arrowPoint = anchorPoint;
|
||||
|
||||
// adjust it to unit coordinates for the actual layer.anchorPoint property
|
||||
anchorPoint.x /= self.frameWidth;
|
||||
anchorPoint.y /= self.frameHeight;
|
||||
self.layer.anchorPoint = anchorPoint;
|
||||
|
||||
// setting the anchor point moves the view a bit, so we need to reset
|
||||
self.frameOrigin = calloutOrigin;
|
||||
|
||||
// make sure our frame is not on half-pixels or else we may be blurry!
|
||||
self.frame = CGRectIntegral(self.frame);
|
||||
|
||||
// layout now so we can immediately start animating to the final position if needed
|
||||
[self setNeedsLayout];
|
||||
[self layoutIfNeeded];
|
||||
|
||||
// if we're outside the bounds of our constraint rect, we'll give our delegate an opportunity to shift us into position.
|
||||
// consider both our size and the size of our target rect (which we'll assume to be the size of the content you want to scroll into view.
|
||||
CGRect contentRect = CGRectUnion(self.frame, CGRectInset(rect, -REPOSITION_MARGIN, -REPOSITION_MARGIN));
|
||||
CGSize offset = [self offsetToContainRect:contentRect inRect:constrainedRect];
|
||||
|
||||
NSTimeInterval delay = 0;
|
||||
self.popupCancelled = NO; // reset this before calling our delegate below
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(calloutView:delayForRepositionWithSize:)] && !CGSizeEqualToSize(offset, CGSizeZero))
|
||||
delay = [self.delegate calloutView:(id)self delayForRepositionWithSize:offset];
|
||||
|
||||
// there's a chance that user code in the delegate method may have called -dismissCalloutAnimated to cancel things; if that
|
||||
// happened then we need to bail!
|
||||
if (self.popupCancelled) return;
|
||||
|
||||
// if we need to delay, we don't want to be visible while we're delaying, so hide us in preparation for our popup
|
||||
self.hidden = YES;
|
||||
|
||||
// create the appropriate animation, even if we're not animated
|
||||
CAAnimation *animation = [self animationWithType:self.presentAnimation presenting:YES];
|
||||
|
||||
// nuke the duration if no animation requested - we'll still need to "run" the animation to get delays and callbacks
|
||||
if (!animated)
|
||||
animation.duration = 0.0000001; // can't be zero or the animation won't "run"
|
||||
|
||||
animation.beginTime = CACurrentMediaTime() + delay;
|
||||
animation.delegate = self;
|
||||
|
||||
[self.layer addAnimation:animation forKey:@"present"];
|
||||
}
|
||||
|
||||
- (void)animationDidStart:(CAAnimation *)anim {
|
||||
BOOL presenting = [[anim valueForKey:@"presenting"] boolValue];
|
||||
|
||||
if (presenting) {
|
||||
if ([self.delegate respondsToSelector:@selector(calloutViewWillAppear:)])
|
||||
[self.delegate calloutViewWillAppear:(id)self];
|
||||
|
||||
// ok, animation is on, let's make ourselves visible!
|
||||
self.hidden = NO;
|
||||
}
|
||||
else if (!presenting) {
|
||||
if ([self.delegate respondsToSelector:@selector(calloutViewWillDisappear:)])
|
||||
[self.delegate calloutViewWillDisappear:(id)self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)finished {
|
||||
BOOL presenting = [[anim valueForKey:@"presenting"] boolValue];
|
||||
|
||||
if (presenting) {
|
||||
if ([self.delegate respondsToSelector:@selector(calloutViewDidAppear:)])
|
||||
[self.delegate calloutViewDidAppear:(id)self];
|
||||
}
|
||||
else if (!presenting) {
|
||||
|
||||
[self removeFromParent];
|
||||
[self.layer removeAnimationForKey:@"dismiss"];
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(calloutViewDidDisappear:)])
|
||||
[self.delegate calloutViewDidDisappear:(id)self];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
// we want to match the system callout view, which doesn't "capture" touches outside the accessory areas. This way you can click on other pins and things *behind* a translucent callout.
|
||||
return
|
||||
[self.leftAccessoryView pointInside:[self.leftAccessoryView convertPoint:point fromView:self] withEvent:nil] ||
|
||||
[self.rightAccessoryView pointInside:[self.rightAccessoryView convertPoint:point fromView:self] withEvent:nil] ||
|
||||
[self.contentView pointInside:[self.contentView convertPoint:point fromView:self] withEvent:nil] ||
|
||||
(!self.contentView && [self.titleView pointInside:[self.titleView convertPoint:point fromView:self] withEvent:nil]) ||
|
||||
(!self.contentView && [self.subtitleView pointInside:[self.subtitleView convertPoint:point fromView:self] withEvent:nil]);
|
||||
}
|
||||
|
||||
- (void)dismissCalloutAnimated:(BOOL)animated {
|
||||
[self.layer removeAnimationForKey:@"present"];
|
||||
|
||||
self.popupCancelled = YES;
|
||||
|
||||
if (animated) {
|
||||
CAAnimation *animation = [self animationWithType:self.dismissAnimation presenting:NO];
|
||||
animation.delegate = self;
|
||||
[self.layer addAnimation:animation forKey:@"dismiss"];
|
||||
}
|
||||
else [self removeFromParent];
|
||||
}
|
||||
|
||||
- (void)removeFromParent {
|
||||
if (self.superview)
|
||||
[self removeFromSuperview];
|
||||
else {
|
||||
// removing a layer from a superlayer causes an implicit fade-out animation that we wish to disable.
|
||||
[CATransaction begin];
|
||||
[CATransaction setDisableActions:YES];
|
||||
[self.layer removeFromSuperlayer];
|
||||
[CATransaction commit];
|
||||
}
|
||||
}
|
||||
|
||||
- (CAAnimation *)animationWithType:(SMCalloutAnimation)type presenting:(BOOL)presenting {
|
||||
CAAnimation *animation = nil;
|
||||
|
||||
if (type == SMCalloutAnimationBounce) {
|
||||
CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
|
||||
CAMediaTimingFunction *easeInOut = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
||||
|
||||
bounceAnimation.values = @[@0.05, @1.11245, @0.951807, @1.0];
|
||||
bounceAnimation.keyTimes = @[@0, @(4.0/9.0), @(4.0/9.0+5.0/18.0), @1.0];
|
||||
bounceAnimation.duration = 1.0/3.0; // the official bounce animation duration adds up to 0.3 seconds; but there is a bit of delay introduced by Apple using a sequence of callback-based CABasicAnimations rather than a single CAKeyframeAnimation. So we bump it up to 0.33333 to make it feel identical on the device
|
||||
bounceAnimation.timingFunctions = @[easeInOut, easeInOut, easeInOut, easeInOut];
|
||||
|
||||
if (!presenting)
|
||||
bounceAnimation.values = [[bounceAnimation.values reverseObjectEnumerator] allObjects]; // reverse values
|
||||
|
||||
animation = bounceAnimation;
|
||||
}
|
||||
else if (type == SMCalloutAnimationFade) {
|
||||
CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
|
||||
fadeAnimation.duration = 1.0/3.0;
|
||||
fadeAnimation.fromValue = presenting ? @0.0 : @1.0;
|
||||
fadeAnimation.toValue = presenting ? @1.0 : @0.0;
|
||||
animation = fadeAnimation;
|
||||
}
|
||||
else if (type == SMCalloutAnimationStretch) {
|
||||
CABasicAnimation *stretchAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
|
||||
stretchAnimation.duration = 0.1;
|
||||
stretchAnimation.fromValue = presenting ? @0.0 : @1.0;
|
||||
stretchAnimation.toValue = presenting ? @1.0 : @0.0;
|
||||
animation = stretchAnimation;
|
||||
}
|
||||
|
||||
// CAAnimation is KVC compliant, so we can store whether we're presenting for lookup in our delegate methods
|
||||
[animation setValue:@(presenting) forKey:@"presenting"];
|
||||
|
||||
animation.fillMode = kCAFillModeForwards;
|
||||
animation.removedOnCompletion = NO;
|
||||
return animation;
|
||||
}
|
||||
|
||||
- (CGFloat)centeredPositionOfView:(UIView *)view ifSmallerThan:(CGFloat)height {
|
||||
return view.frameHeight < height ? floorf(height/2 - view.frameHeight/2) : 0;
|
||||
}
|
||||
|
||||
- (CGFloat)centeredPositionOfView:(UIView *)view relativeToView:(UIView *)parentView {
|
||||
return roundf((parentView.frameHeight - view.frameHeight) / 2);
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
|
||||
self.backgroundView.frame = self.bounds;
|
||||
|
||||
// if we're pointing up, we'll need to push almost everything down a bit
|
||||
CGFloat dy = self.currentArrowDirection == SMCalloutArrowDirectionUp ? TOP_ANCHOR_MARGIN : 0;
|
||||
|
||||
self.titleViewOrDefault.frameX = self.innerContentMarginLeft;
|
||||
self.titleViewOrDefault.frameY = (self.subtitleView || self.subtitle.length ? TITLE_SUB_TOP : TITLE_TOP) + dy;
|
||||
self.titleViewOrDefault.frameWidth = self.frameWidth - self.innerContentMarginLeft - self.innerContentMarginRight;
|
||||
|
||||
self.subtitleViewOrDefault.frameX = self.titleViewOrDefault.frameX;
|
||||
self.subtitleViewOrDefault.frameY = SUBTITLE_TOP + dy;
|
||||
self.subtitleViewOrDefault.frameWidth = self.titleViewOrDefault.frameWidth;
|
||||
|
||||
self.leftAccessoryView.frameX = ACCESSORY_MARGIN;
|
||||
if (self.contentView)
|
||||
self.leftAccessoryView.frameY = TITLE_TOP + [self centeredPositionOfView:self.leftAccessoryView relativeToView:self.contentView] + dy;
|
||||
else
|
||||
self.leftAccessoryView.frameY = ACCESSORY_TOP + [self centeredPositionOfView:self.leftAccessoryView ifSmallerThan:ACCESSORY_HEIGHT] + dy;
|
||||
|
||||
self.rightAccessoryView.frameX = self.frameWidth-ACCESSORY_MARGIN-self.rightAccessoryView.frameWidth;
|
||||
if (self.contentView)
|
||||
self.rightAccessoryView.frameY = TITLE_TOP + [self centeredPositionOfView:self.rightAccessoryView relativeToView:self.contentView] + dy;
|
||||
else
|
||||
self.rightAccessoryView.frameY = ACCESSORY_TOP + [self centeredPositionOfView:self.rightAccessoryView ifSmallerThan:ACCESSORY_HEIGHT] + dy;
|
||||
|
||||
|
||||
if (self.contentView) {
|
||||
self.contentView.frameX = self.innerContentMarginLeft;
|
||||
self.contentView.frameY = TITLE_TOP + dy;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// import this known "private API" from SMCalloutView.m
|
||||
@interface SMCalloutBackgroundView (EmbeddedImages)
|
||||
+ (UIImage *)embeddedImageNamed:(NSString *)name;
|
||||
@end
|
||||
|
||||
//
|
||||
// Callout background assembled from predrawn stretched images.
|
||||
//
|
||||
@implementation SMCalloutImageBackgroundView {
|
||||
UIImageView *leftCap, *rightCap, *topAnchor, *bottomAnchor, *leftBackground, *rightBackground;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
leftCap = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 17, 57)];
|
||||
rightCap = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 17, 57)];
|
||||
topAnchor = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 41, 70)];
|
||||
bottomAnchor = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 41, 70)];
|
||||
leftBackground = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 1, 57)];
|
||||
rightBackground = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 1, 57)];
|
||||
[self addSubview:leftCap];
|
||||
[self addSubview:rightCap];
|
||||
[self addSubview:topAnchor];
|
||||
[self addSubview:bottomAnchor];
|
||||
[self addSubview:leftBackground];
|
||||
[self addSubview:rightBackground];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UIImage *)leftCapImage { return _leftCapImage ? _leftCapImage : [[SMCalloutBackgroundView embeddedImageNamed:@"SMCalloutViewLeftCap"] stretchableImageWithLeftCapWidth:16 topCapHeight:20]; }
|
||||
- (UIImage *)rightCapImage { return _rightCapImage ? _rightCapImage : [[SMCalloutBackgroundView embeddedImageNamed:@"SMCalloutViewRightCap"] stretchableImageWithLeftCapWidth:0 topCapHeight:20]; }
|
||||
- (UIImage *)topAnchorImage { return _topAnchorImage ? _topAnchorImage : [[SMCalloutBackgroundView embeddedImageNamed:@"SMCalloutViewTopAnchor"] stretchableImageWithLeftCapWidth:0 topCapHeight:33]; }
|
||||
- (UIImage *)bottomAnchorImage { return _bottomAnchorImage ? _bottomAnchorImage : [[SMCalloutBackgroundView embeddedImageNamed:@"SMCalloutViewBottomAnchor"] stretchableImageWithLeftCapWidth:0 topCapHeight:20]; }
|
||||
- (UIImage *)backgroundImage { return _backgroundImage ? _backgroundImage : [[SMCalloutBackgroundView embeddedImageNamed:@"SMCalloutViewBackground"] stretchableImageWithLeftCapWidth:0 topCapHeight:20]; }
|
||||
|
||||
// Make sure we relayout our images when our arrow point changes!
|
||||
- (void)setArrowPoint:(CGPoint)arrowPoint {
|
||||
[super setArrowPoint:arrowPoint];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
|
||||
// apply our background graphics
|
||||
leftCap.image = self.leftCapImage;
|
||||
rightCap.image = self.rightCapImage;
|
||||
topAnchor.image = self.topAnchorImage;
|
||||
bottomAnchor.image = self.bottomAnchorImage;
|
||||
leftBackground.image = self.backgroundImage;
|
||||
rightBackground.image = self.backgroundImage;
|
||||
|
||||
// stretch the images to fill our vertical space. The system background images aren't really stretchable,
|
||||
// but that's OK because you'll probably be using title/subtitle rather than contentView if you're using the
|
||||
// system images, and in that case the height will match the system background heights exactly and no stretching
|
||||
// will occur. However, if you wish to define your own custom background using prerendered images, you could
|
||||
// define stretchable images using -stretchableImageWithLeftCapWidth:TopCapHeight and they'd get stretched
|
||||
// properly here if necessary.
|
||||
leftCap.frameHeight = rightCap.frameHeight = leftBackground.frameHeight = rightBackground.frameHeight = self.frameHeight - 13;
|
||||
topAnchor.frameHeight = bottomAnchor.frameHeight = self.frameHeight;
|
||||
|
||||
BOOL pointingUp = self.arrowPoint.y < self.frameHeight/2;
|
||||
|
||||
// show the correct anchor based on our direction
|
||||
topAnchor.hidden = !pointingUp;
|
||||
bottomAnchor.hidden = pointingUp;
|
||||
|
||||
// if we're pointing up, we'll need to push almost everything down a bit
|
||||
CGFloat dy = pointingUp ? TOP_ANCHOR_MARGIN : 0;
|
||||
leftCap.frameY = rightCap.frameY = leftBackground.frameY = rightBackground.frameY = dy;
|
||||
|
||||
leftCap.frameX = 0;
|
||||
rightCap.frameX = self.frameWidth - rightCap.frameWidth;
|
||||
|
||||
// move both anchors, only one will have been made visible in our -popup method
|
||||
CGFloat anchorX = roundf(self.arrowPoint.x - bottomAnchor.frameWidth / 2);
|
||||
topAnchor.frameOrigin = CGPointMake(anchorX, 0);
|
||||
|
||||
// make sure the anchor graphic isn't overlapping with an endcap
|
||||
if (topAnchor.frameLeft < leftCap.frameRight) topAnchor.frameX = leftCap.frameRight;
|
||||
if (topAnchor.frameRight > rightCap.frameLeft) topAnchor.frameX = rightCap.frameLeft - topAnchor.frameWidth; // don't stretch it
|
||||
|
||||
bottomAnchor.frameOrigin = topAnchor.frameOrigin; // match
|
||||
|
||||
leftBackground.frameLeft = leftCap.frameRight;
|
||||
leftBackground.frameRight = topAnchor.frameLeft;
|
||||
rightBackground.frameLeft = topAnchor.frameRight;
|
||||
rightBackground.frameRight = rightCap.frameLeft;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SMCalloutBackgroundView (ClassicEmbeddedImages)
|
||||
|
||||
//
|
||||
// I didn't want this class to require adding any images to your Xcode project. So instead the images needed are embedded below.
|
||||
//
|
||||
|
||||
+ (NSString *)SMCalloutViewBackground { return @"iVBORw0KGgoAAAANSUhEUgAAAAEAAAA5CAYAAAD3PEFJAAAAHGlET1QAAAACAAAAAAAAAB0AAAAoAAAAHQAAABwAAACkF8Y1LgAAAHBJREFUGBlMy6ENQyEARdGXJh2CBEZAgAABhoQgEASDQBCWYqy3wt/lt80XFfe4CwCvT3gjpXThnEPsvYm1FjHnJMYYRO+daK0RtVailELknPndiBjjQwiB8N4TzrkHay3xwxhDaK0JpRQhpfxHCMEbAAD//2TbkE8AAAA9SURBVGOQlJQ8w0AJIScnd4ZBQ0PjDIOysvJdBiCIARFhIMIPRLiACEsQoQMiFEGEBIgQABFcIIIVRDABAL6ePTfutWY6AAAAAElFTkSuQmCC"; }
|
||||
+ (NSString *)SMCalloutViewBackground_2x { return @"iVBORw0KGgoAAAANSUhEUgAAAAIAAAByCAYAAAB5lADlAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAHGlET1QAAAACAAAAAAAAADkAAAAoAAAAOQAAADkAAAD3bjNTjgAAAMNJREFUOBGkz72JhGAAhOGpwyZUVESFVfAPFFRcFfypwmJsa4KNtxaPucAvuuiCZ3jTwX3fkN/5I7Iso2Acx4/guq6v4DzPj+A4Dgr2faeY2LaNgifWdaVgWRYK5nmmmJimiYL3+03BMAwU9H1PMdF1HQVt21LQNA3FRF3XFFRVRUFZlhQURUFBnucUPJfTNKXgidfrRUGSJBTEcUwxEUURBWEYUhAEAcWE7/sUeJ5HMeG6LgWO41Bg2zbFhGVZFPwvfgAAAP//7zgpOQAAAGxJREFU5cYhCkJBAEXRqyAYLJOECTLFaDXafjNZDIKgLniWNL73eXxwDYZzudRau/HP01rrxjKllG4sA3yCt8Z4BU+N8QjuGuMWXDXGFFw0xjk4aYxjcNAY+6BojF2w1RibYK0xVrMxBjbnZ75mdyKsdyBPxAAAAABJRU5ErkJggg=="; }
|
||||
+ (NSString *)SMCalloutViewBottomAnchor { return @"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"; }
|
||||
+ (NSString *)SMCalloutViewBottomAnchor_2x { return @"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"; }
|
||||
+ (NSString *)SMCalloutViewLeftCap { return @"iVBORw0KGgoAAAANSUhEUgAAABEAAAA5CAYAAADQksChAAAAHGlET1QAAAACAAAAAAAAAB0AAAAoAAAAHQAAABwAAAKS6krCNQAAAl5JREFUSA2klNmKGkEUhs0yM9nIglnctQc3zKioqOCKwQVXFBRUcLnxwvtci88gPkpeIlATEshVSM2rmP9UUo3dajuQi49TVX3OZ3mquk273c50DpPJ9OAID7H2l2MCXYFMfoT1xzouML842MU/gabQ4XAUo9Ho10KhcFcqlbgejWRPIH/10ul0fq7X67/X6/X3zWZzu91umR5VohPQNq/MZvOndrv9a7VafVssFmw+n7PZbKYi50JyTIC1Zz6f78tyufwxnU7ZZDI5iV5CjbsiAXiZy+V+jsfjW8BGo9FJ9iXUh0vwlATAnEwm74bDIRsMBoZICZ2G3MULjN8ASzgc5v1+n3W73QN6vR4j6BlyxUUiCTVT7uIdxo5QKMQpqdPpaGi1WmIuI3KFRP6V55iLXSAqgUCAUyKOmDWbTdZoNESUY5oT+xJqKP0VM7ADL06HU0GtVlOpVqtMIteRK3ZC/XgCqKFvgRMEvF4vp4JyuWwIcg8k77HmBiFFUXilUmG45oaQRJ4MNfUV+AA84KPH46H3hBWLRZbP50WUY5pLkKuRvMbcAhRwQxIqwqUzBLlCQsdLt1QjcblcnASZTEZDNptV5zQ+JbnGgxuSUFI6nTbESBLGZ4DTLlKplGBfJtconpVQId4hVUTj/flZCb5oPBaLsXg8LkgkEuwYhjux2+0cF06VkExKZaS1sxJcOOb3+0UxFR7DUGK1WjlJcEqMYjAYZPg8sEgkouGsxO12M5wSQ380kdYk95KQwIj/kthsNiE3lFgsFk79oGRZIMcy0g7vJUGDVZEslpGe/QEAAP//KqClBgAAAfBJREFU7ZTRatNQHIfjdFaj1El1prW2Sbti2aZeKEMFB+pAEFREFAa79d5n8YF8gkzUi93M7VXq9x08I82y7QH04iNpOP34/37nJEmSJAuwCCksQQYjuJdl2cFgMCh7vV6g2+2WVXzub9aeLan+sen+v2S+3H+12Hioms7IWZ2ss+AwntiTBKdJCk6hkr08z3f7/f7cca8L44m9wM1liO9OkHQ6nW/j8fjncDgsFZ0Uqy65xoNbkMNamqafEexPJpPvo9GoNFoTrE3OgZNcgjYswxBWYaPdbn9lmt/T6fQH7EJZh3XHJDd4dgfuwkPYbLVaX4jyqyiKQ4QHdVgTJOe5tuAqdOA2rMADeAov4DW8h0+wDTsVjiQXeXgFroMfJstdg0fwDLZA0Tv4AB9BoQRJ/Lq5Q/ZyE/owgfuwAYpewitQ9gbeBmazGdfwdbPcGClOY8F2o8iJjLYJz0Gh020lfyXukL0YKU5jN11Q5ERGsyPLdrLH8CRQkRgpTpNybyxF9mM0O1oBJ1sFpeuBmsRp/PIbK4qMZkfK3DW33+lyUFyEOKeIjOa2O5UyJ1O4DJ5sxdmRpEFkNDvyJCtz+6NQqa+I79rSnKQmsiPjKTOiQmMqjWLl6TFJReSOibKqUGlE+WKjRFEVFkZh9RrlC38A3S8C8jPZQY0AAAAASUVORK5CYII="; }
|
||||
+ (NSString *)SMCalloutViewLeftCap_2x { return @"iVBORw0KGgoAAAANSUhEUgAAACIAAAByCAYAAAA2yQM1AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAHGlET1QAAAACAAAAAAAAADkAAAAoAAAAOQAAADkAAARX3Ik1YgAABCNJREFUaAXsmFlLJGcUhjtOJhOTSWbSMTpRMyhpNdqJCyruG2644L7gCoqKInihqFeCeqP3gqLxLpAfkZ8QCMelmUwWk/wT875FTlPT0zpV39cTTFB4OFZp9fd4zqmvThm4vr4O3AXuhAQTcS8S2w5vLSNVVVUZDQ0NM01NTcfge/BTY2Oj3ETCRerr60uw2CkkJB49PT0X/f39lwMDA6+QMJHS0tKHkNjUxVtbW2Vvb+/q9PT0L/DnycnJH8fHx1dHR0dRDg8PrxRrkUAg8E5xcfFTSESzsLOz89vBwcHvEPlla2vrxebmZmR9fT2ytrZ2ubq6GhdjEQqQzMzMZGThO2aipaVF9vf3f93e3v4Zi0aWl5cv5ubmzmdmZs6mp6eFTE5OxsW3iAr8E5Pq6uq2kA1hKXZ3d19ubGxElpaWnMW56MTEhCd8ibgkkvB9Unl5eZgSBAIvkPbIwsLCOf7yMz8SlPUsEiuB4we1tbUnQLq6uoQS8/Pz51NTU2fj4+PiF08iMRIPcPwus0EJ7A+ysrJyubi46GTCr4D+/htF4kng3MPq6uqVmpoa6ezsFEhcsCH5oWNjY0Z4FXF6gpmgBHhUWVn5LWRkcHBQZmdnz9ETZyMjI2LKrSJY0LlFESnCkjgSiMmQ+IEiXJjNyTg8PGyMF5HXJCDyGM+SH4FTBpbERoLX3iiCxZgNd0new/H74EPwhBIqwr4YGhqywouIuyQfQOJjEKyoqBAyOjrqwF6xwYsIG5TZSAaPwVPwGW5fIVqSvr4+sSGuCBbSssTLxqf4+bNYkd7eXrHBi4g7G59AIhVk/NsiLMsjoL3hZAPHz1VEmxQDj9hwW0bcZeGd4vQGYjrIxiAkBNOWA583Ntwk4t47tEm1LJkQCamINii3ehveJKL98REWD4Jn4DnILSkpEaINaiPBa72IuPvjc0hkga8wHgrRvmhvbxcbXhPBInrruhv1Cc6nAKc/EPOLioqEdHd3O7S1tYkNXkTcjZoBiS9BgYpoSTgq2uBHhPsHG5Ui4cLCQiEqwsHZhttE+MjnQ063dRUJ4dzX+BLS0dHh0NzcLDZ4FeGtmwa+AI5IOBwWon1x06uk1/OmIt8UFBQI0b7QNzzT+J8V4WaWA6IZ0QbV9xvT6DcjUZH8/HwhfJ0gnOhtMBbJy8sTos3IQdoGY5Hc3Fwh2pw2ErzWWCQnJ+cVER2mTaOxSFZWllBGm1OHadNoLIL/i0goFIo2qE5sptFYJDU11RHR3jAV0OuMRVJSUoTlUZGysjKxwUokLS3Nedtjg+roaBqtRJgVziV3QoTl0fnVJlpnhCLZ2dnOnqJzrElMiAhl0tPTnebljKJjpJ+YUBHKqBC3fz9Sb0VEhfzEe5HYbN1nJGEZCQaDQnjr6u0b++F+jo1L878V+RsAAP//Pedk8QAABFZJREFU7ZndclNlGEYDNQUloWlMCU1CGn6qAqIowjgoyp+joDhqwb/6g3oDHnkH3oLjoWceeOKMjgeeeAEe9JLqWkle83W7k+4QZso46cyaZDfdey+e9/m+NqG0vb1dSimVSvtgP5ThIFRgGZrQhXU4V6/Xt6TX6/VptVpbs7BDQiG+5iI7UpknkhZ13hFW3I5+zBOZJ5KzC887kv29NE9knkg2gezxvCP/y0T+Xl5e3lpbW9vbv1lrtdpfj4QIEj8r0ul0+om02+29+Su+Wq1+r8jq6mpfpNvt7o3I4uLihiIrKyt9Ed/fZFfCNMfT7iPH+Cv/FJyDS0tLS7+kqVjcaW6e/uxMIqSyqYjE6nlQmQcVeZZELsIVuvJjjGgWmaIiNW56BDrgaP4V4fktlvJvWRk7Y4GLrqZJIo9xkwNwCFKRkxyfhZfgVbi5sLBwD5lflfGNeaykeINe5HEakRVu2gZFzsAFeAVuwG3YYEw/KSONRmOr2Wz29xmT2U2miMgT3GQJGtCC43AaXoTLcB1uwbtwlwJ/x2r6I4SKPv5HZPgmy89HFmARFDkMT8Iq9OAZOA8vw1V4E+7AB/ARbJbL5W8rlcoPjOx3xP7cTaiIyONcuAp1OAp+WPMUPAeX4DV4A96G9+AefAKfw5fwFXw95Bse88m+Cc8k4qdGisSnRq6c2NQsbPTE8bwFjmcD+qnwGDL3ea5QKhVyg8cxIvs4ydEokq4cCxs9ScfzOt9PU7nL8cfwKSjzBZjO/RwGghNE9nNSLOG0JzGedV53q3cZu3qugV15BxyRMibjmDbhM1AqxJQbsYuIqVjY7HhcxifgNDwPdsU95TqkMo7JzoSQUqaUouTmuLI6GhNJx5OXirusXXkBXEFXIGQsr50xHVeTCSklHw5RcEBeIn6Pr5BxPGkqNY6jKz2eu4Lc8t1XlDEZx2RnLLBCLu2QUuz9IQoOKCCSl0qdCzShA8fhaVDGZByTnbHApqOQ41LqNigW2KcBBUQcUZrKIY7dad3gUhmTOQt2xgKbjkKO6yoodQNuDlFwxDiRzHjSVKK4jiiV6XFsZyywq+k8XAATUuoyKObolBM3wwEFRKIru8m0uGgXTsA6uM+YkDuwUnZIMdO6OETJAZNEMqnEiMqcfAAiGcdkZyzwUWiDQnbnJDgypUzqDChnn8TkBhQUSVOxL6mMnTkMMaojPFfIhCyzUj1QzLSUc4Qp67n7SFaOkxQZJ3OQ19xjKqCQ/5Nhd0wopPytrZhpKSfHErqFRJIRpTLRGfeYGFUqZEJKOTbFGhByCrriRmT/9ZOOOTFNxs4oE6PKCjkyU6qCSdkl5QIlR0y6cd5rnBwykc44oUjJUpuUKBeCSo7Iu1mR73GREFIkMKE0JUttUpGWcmKvdlLkppN+hguGUJpQpBRiIecYY5RKjph0k2lfy0iFYKQ1+XHam83y82NEB8KzXPhhnlt4H3mYN8271iMj8g/zleowQQBJWQAAAABJRU5ErkJggg=="; }
|
||||
+ (NSString *)SMCalloutViewRightCap { return @"iVBORw0KGgoAAAANSUhEUgAAABEAAAA5CAYAAADQksChAAAAHGlET1QAAAACAAAAAAAAAB0AAAAoAAAAHQAAABwAAAKdevXfpAAAAmlJREFUSA2UlNlqWlEUhredJzpgB41zcAi2JiRiBE0UiwNxQsGACibeeJH7XovPEPIofYnCSmmhV6XJq6T/f3SfHg/xmF58rH22rm+vvfY+Ryml7llwYbzEzc2NWgdy1MMFDxCt3MezXsAU3yZUpVLpyk6hULje2dn55vf7iwvxktAuUhcXF2Ln/Pz8cjab/Tg6OvoTCAS+QPTIJnNZRer09FTG47Ewavg8mUxkOp1+b7Vav91u92dIHgNuXVdlitRoNJJVnJycyNnZ2c9YLPYVyc9WidRgMJBVDIdDAZcHBwe/IHhpEfEA2HSjGnV8fCxO9Pt9yWQy10hwA4qeAvaI25pLOp2OdLtdA47t9Ho9SaVSV0jwgDfgBWB/jGrYYNVut6XZbIo1cqyhNJlMUuIH74Cuhk02tqTq9bpoGo2GED4z4oiNBRKJBCURoKt5jrG5JVWr1YRUq1UTPcdIGU6HkijwAfZGb8noiyqXy+IE5dFolJIECIC3gFt6AtgXF6+9OFGpVCQSiVCSBCHwHixLDg8PxUqxWBTCOUYuEA6HKfkIwuADeAV41PP7goskTlC0kHxCkm7u6yVJPp+XXC5nYB3rOS4QDAZZiV3C12B+zNlsVpyg2CLZRCKPmZX8k+zv74vGKtNzrAifA1aSAusleEf4nphSjileK0mn03Ibe3t7QnZ3dwVfOOdKrH/WSXqOERdNfD6fs4QrrSIej/OirZdsb2+LFbz2srW1ZSTjVIzo9XqdK0HT2DgT7N8Y6xgKhWSthH924s6SjY2NlaL/klCkYWV6zL54PB7nnmC/ZoJO1JG/3UXyFwAA//9HuzCQAAAB80lEQVTtlc1qU1EURk9iNXqVqERrEmNykzQY/B0oooKCPyAIbRGpIDh17rP4QD7BragDJ9q+SlwrZsd7NRQFhx0szulhf9/Z+7uHJnU6naLb7RauZTyTfr9ftNvtvZTSdRhBG85ABkehnsrCVftDk2q4ZnSYSTWT//LYwoSA93mZ1+DfX2yv1yvyPN/F5OvCZMj6d8/eDjQYDAbFeDz+3Gq1PqwwOcHZGtST72AVo9GomEwmHzH6lmXZW4qvQg4X4DT8MplOp8UKdjn7RBffm83mewR34AoMYB2acBzspJYo3Pud4XC4z0hfGo3GO4oewi24DJfgHFRNOHhT4jX7V/ACnsNjuA83YQMuQgtOQQOOQA3mIoU78BK2QYOn8ABug3nElznL/iQcg6XJFn/IJih+Bk9AA7O4ARPowXlwFEP9+V9t0Yk3isJHYAaOYAcamIWB+j7sIkZZY1+fzWYsKd1bcJfVmw3RDBzBDjTogFlEF8tRwsTnLIr8jN68AWbgCHYQBhl7A513wVoLE4slB2/1M/oVFJuBI9hBGJiFgdZhaWKx+BLXQaE3h9gMDNIO/jCITvwNEZ+yN4pCP6NiX6YZOEKlAw3CxDYtDoEib1XozWXxcoQwCBMLo1hBELfOhZzXpCyOPefzgCysFCuQKDxo/QHd3ALyX+9lLwAAAABJRU5ErkJggg=="; }
|
||||
+ (NSString *)SMCalloutViewRightCap_2x { return @"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"; }
|
||||
+ (NSString *)SMCalloutViewTopAnchor { return @"iVBORw0KGgoAAAANSUhEUgAAACkAAABGCAYAAABRwr15AAAAHGlET1QAAAACAAAAAAAAACMAAAAoAAAAIwAAACMAAAQYDCloiwAAA+RJREFUaAXMVslKHFEUfSaaaPIFunDaulJj2/QQBxxwVtCFC9GFCxVRUHHAARVx1pWf9QKVNCGb5FvMPUWf5vazqu0qFVwc7nTuvaeedr0yj4+P5qUwxlQoVCq/4qWz0R9boBLyQXwfzc3N31paWn7CMpe3/kPEFRxLZF4gxX2UuLKpqSkxOTn56/T09DcsYuQFqJMb62Qji5SFOBUs9cWJ/dTY2JiEsPv7e+/s7OwHLGLkURdosZGFRhIpyygQS6sE1Q0NDampqakchMkpWgIx8qiDl+ejDw8YSWjZIjE4vwCLcDrV9fX1aQi5u7vzBZ6cnBREQiyFggd+vi+y0LJEynBXYI0szsif1BcIccfHx0+APB4APPBlTk0coc+KDBM4MTGRu7299SDk6OgoFKiDB35coSVFhgkcGxvLXV1deRB3eHhoDw4OiixyGjjlm5sbD31xhIaKDBM4Ojqau7y89AXu7+/bvb09C/sc8EDoQ39UoYEiwwQODw/nLi4uPJzc7u5uZKAP/SMjI5GEPhEZJnBoaCh3fn7uQdzW1pbd3t72resjZg4cgnn0Yw7mlXuiRSKDBNbV1WUwUF4p3s7Ojt3Y2CjC5uamH8NqHzzmXIs5mIe5mC97S/7qpV64svQt8lnyX2pra7MDAwO+QCxaW1vzsb6+bgk3FxSTqy3myS/fw3zswT4B9uI9qq9S6PJvDtweeEGDhKf6Ko3f+/v7c/LL9DB8dXX11YG5mI892Ie9+f3QAT3QVWV6enr+uejt7f07PT39R14j3srKil1aWirC8vJyUezW3bgUH/OxB/uw19WC2Dw8PNggXF9f+0IWFxftWwMPhX1BOpAzCwsL1sX8/LwFmKdPyzxsGJecoB7Wwix7aM3c3Jx97zCzs7P2vcPMzMxY+ae1sHERpd/lunGQBiOfUVa+CS2si7C85mlOmB+2o1y+GR8ft4Bc/EXWzSOWz60iDnuCuMwFWfbBBvnoYR6+kY8GqyGXvx/D0medsVuT660wgxy3h7Fr2cs+WvJQN3It2cHBQR/wCZ2DX4rHGnvd2J3lztN13Uue6evrs4C87X0bFjP/mtbdGTYb16Iluru7C36pHGvavqRXz6Gv55lsNmuJrq4uq4E8YteSwz7WycUCnWMeljW3l3zWdY/JZDL2vcOkUikbhGQyWcin0+mCH8SNmtPz6Ot97jzT2dlpCRDpw7qxrr2l7+41iUTCamA5Y/raUhw5sMyR59Y0R9eYD8txXpHIjo4OXyCtbg7K6Tr953il6m6NsWlvb7floq2trWyunhm3jzMMBgShtbXVAlFq5MfpxR7dz73I/QcAAP//R6Pt0AAAASZJREFU7VK7asNAEBwCblKltISkImrUWEhl+tQuU+WT7xfyL94TjNksdzIkZ7yGMwyzj9m9YWWs6xpuYVmWm5rcjhKzmOc55BAfYE/HrO2x1ut4b4Y9rY8x+r4PFl3XhQjWGVtmX/M9ZsGlnnkz2bbtdjkaZU5mPcVWY/PUDGvUWmafjCjQIuY5joO2l6pZTcxTulTNzl5NNk0TiCjKxXaB1sYZndv4r7PQi/VSmiTzAeZkzpBZ1/zf2c0kF+49RE2O7zn7y2TOwKPr1WSpL1AvWS9Z6gKl9tT/ZL1kqQuU2oNhGIJ3YJqm4B0Yx/HHOyC/7ycAvsSkd+AsJr0Dn2LSO/AhJr0DJzHpHXgXk96Bo5j0DryJSe/Aq5j0DhzEpHfgRUy6xgX1iUkAQX47jAAAAABJRU5ErkJggg=="; }
|
||||
+ (NSString *)SMCalloutViewTopAnchor_2x { return @"iVBORw0KGgoAAAANSUhEUgAAAFIAAACMCAYAAADvGP7EAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5QUY0RkNERjZENDMxMUUyQTAzNEREMUIxRjIzOEVCNSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo5QUY0RkNFMDZENDMxMUUyQTAzNEREMUIxRjIzOEVCNSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjdFQUM5NTk0NkJGQjExRTJBMDM0REQxQjFGMjM4RUI1IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjlBRjRGQ0RFNkQ0MzExRTJBMDM0REQxQjFGMjM4RUI1Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+zk9a9AAABhtJREFUeNrsnclO41gUhm3HDGEo5jGAQLwDsxjFICR2JVFVza4fAVFdD9H1ALxA9yPAhg0S6170mnmeCVMSyNDnt3LT7jSIQJzEgf9IVkJw7OuP/5zz3+sAeiwW0xjph0EEHwTkzMzMl+np6W8EmSZEKT2/GYax4HaYhtsh1tfXa9jcDtOVICcnJ78qiCMjIxVNTU12mL+4ccy6G7q2LqGeT0xMfCkoKPgOcH19faUyPl0AahsbG7eHh4faycmJFo1Gf19aWvpDvSfmgovIKUg7QHwpEGcBsaamRuvq6vIGAoHow8ND1OPx6GVlZZ69vb3AwcGBdn5+rmD+CY5uAJozkDaIuk2JCwri/f195Pb2NhIOh2PYtbS01PPp0yfTDjMSifxcXl5WyozlEqbhFoiiuoXq6moL4t3dXeTm5iYSCoWi2ESVMUD1+/1hn8/nRc3EvnKY+ampqW/2YyWp/P2CTIKoK4i1tbVad3e3BRHQkNJQIwQmaRx7fHyM4XvX19fhlpYWb3Nzs4b32GDquYSZ1dR+AuIsICKdFURswWAwKmkbE4D//sSl4cimS/rrJSUlnsrKSnN3dzeABnR2doaURpqrmpn1NM8ayKcgyuMCurNdiYAIJT53HDQe0zSt5qNgomaim6Nmrqys5ASmkU8QEVAq9sH+V1dX4dbWVivNcSyBPD82NvY1F2mecUUmQxSDPSuKWqirq9N6e3u9AKIgAlIq48EhlTLRze1pfnx8jJqadWUa+QYxDiWhTKhZKRPdvKGhAbU068rMmCKTIY6Pj3+Wxx+A2NPT44134ER3tjeWlFUgDciuzKqqKnN7e9tS5unpaVaVmRGQyRCHh4c/ywX/gF2BEmG2FUTYmnTGgFOhmxcWFloNqKKiwtzZ2QkcHR1ZaS7H/7m6uppxmEY+Q1RpDp9pN+1tbW3exsZGK83FLs0PDg5mPM0dVeRzEOET+/r6EjMWzKGTfWLaioj7zKKiIkuZmE6qNIfPzLQyHQP5HESpW1p/f/9/IL5kcdIJ1EzALC8vT8BUc/NMwnQE5FMQ5SVLiYCourOk9au6czo1U8FEzdza2grs7+8nFjoyAdN4TxDtNTMUCsWQAaiZ7e3tXp/Pp2FMMO2ZqJl6mh3zRYi4mGxBfM60S4pb1mhjY8OqmZlQ5ptBJkMUcFZNhE+0Q1Q1UQae9aUtGU9iocMOE9ZIzc3X1tYcgfkmkE9BlAFbFmdgYCDRWOTREYvjhDLhMxXM9fX1ADxm/LaFIzCN9wzRPp2Ez4R/vby8DHd2dnrhMeM31OblGtKuma9S5HMQYXGGhoasdMZgAVEtyrollDJVN6+urraUqXxmuso0nYIIFdohai4LMFFzenluFWwoUx4CeC4woUwtDtN6C645VZh6istW/4OIBQhAHB0d9YrFgM2wIMJ6ODljcXxObFtpr6ysTChT+UyJNykTUP56y4A6OjoM3KiSkz/mC8TnYEp9NzFusUOBOMzXl465ubm/X9oJJ5ST4YQFXq/XIy/BF4YvLi7CqrHkyuKkY41QM3FtUjMN8b6mdPUCfAu+F2ucku5huTaI42VFLi4urqc6WxBQUXWLVLpfojOjI+bj5yztDai4uFiX6aSnpKTEgFhg5BFQbkrNZnNz8z7FYm3BwtQrGAxalgLPFeh8DCUQZJNciw5hCEQLLNL/NVYIn1wIax88ABSZJZsmM7E3qcLkR58damBE4EyY+WBXqMiPpEjWSKY2FckayWBqM7XzBWQ+rdhQkWw2DCqSXZuKJEgGU5sgaX8YrJEEybk2Fclgs6H9IUgGayRrJBXJGsmgIgmSIAmSQftDRdL+MJjaBOnW1MZv0TOoSIIkSAZBEiRBEiSDIAmSIBkESZAESZAMgiRIgiRIBkESJEEyCJIgCZIgGQRJkARJkAyCJEiCZBAkQRIkQTJSD7OwsJAUqEgXKdLv95MCFemewL9h+pUYHEhtLf6//xgE6RqQ/HN9BOkukGFicAbkIzE4A/KBGJwBGSIGgnQVyCAxOAMyQAxUJGskQTLoIzmz4Vz7Y4Hk6o8DgVsNvG/jQHiIwCFF8i+aOhNMa4J0V/wjwADkbTd31/iGkwAAAABJRU5ErkJggg=="; }
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// Custom-drawn flexible-height background implementation.
|
||||
// Contributed by Nicholas Shipes: https://github.com/u10int
|
||||
//
|
||||
@implementation SMCalloutDrawnBackgroundView
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.opaque = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// Make sure we redraw our graphics when the arrow point changes!
|
||||
- (void)setArrowPoint:(CGPoint)arrowPoint {
|
||||
[super setArrowPoint:arrowPoint];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
|
||||
BOOL pointingUp = self.arrowPoint.y < self.frameHeight/2;
|
||||
CGSize anchorSize = CGSizeMake(27, ANCHOR_HEIGHT);
|
||||
CGFloat anchorX = roundf(self.arrowPoint.x - anchorSize.width / 2);
|
||||
CGRect anchorRect = CGRectMake(anchorX, 0, anchorSize.width, anchorSize.height);
|
||||
|
||||
// make sure the anchor is not too close to the end caps
|
||||
if (anchorRect.origin.x < ANCHOR_MARGIN_MIN)
|
||||
anchorRect.origin.x = ANCHOR_MARGIN_MIN;
|
||||
|
||||
else if (anchorRect.origin.x + anchorRect.size.width > self.frameWidth - ANCHOR_MARGIN_MIN)
|
||||
anchorRect.origin.x = self.frameWidth - anchorRect.size.width - ANCHOR_MARGIN_MIN;
|
||||
|
||||
// determine size
|
||||
CGFloat stroke = 1.0;
|
||||
CGFloat radius = [UIScreen mainScreen].scale == 1 ? 4.5 : 6.0;
|
||||
|
||||
rect = CGRectMake(self.bounds.origin.x, self.bounds.origin.y + TOP_SHADOW_BUFFER, self.bounds.size.width, self.bounds.size.height - ANCHOR_HEIGHT);
|
||||
rect.size.width -= stroke + 14;
|
||||
rect.size.height -= stroke * 2 + TOP_SHADOW_BUFFER + BOTTOM_SHADOW_BUFFER + OFFSET_FROM_ORIGIN;
|
||||
rect.origin.x += stroke / 2.0 + 7;
|
||||
rect.origin.y += pointingUp ? ANCHOR_HEIGHT - stroke / 2.0 : stroke / 2.0;
|
||||
|
||||
|
||||
// General Declarations
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
// Color Declarations
|
||||
UIColor* fillBlack = [UIColor colorWithRed: 0.11 green: 0.11 blue: 0.11 alpha: 1];
|
||||
UIColor* shadowBlack = [UIColor colorWithRed: 0 green: 0 blue: 0 alpha: 0.47];
|
||||
UIColor* glossBottom = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 0.2];
|
||||
UIColor* glossTop = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 0.85];
|
||||
UIColor* strokeColor = [UIColor colorWithRed: 0.199 green: 0.199 blue: 0.199 alpha: 1];
|
||||
UIColor* innerShadowColor = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 0.4];
|
||||
UIColor* innerStrokeColor = [UIColor colorWithRed: 0.821 green: 0.821 blue: 0.821 alpha: 0.04];
|
||||
UIColor* outerStrokeColor = [UIColor colorWithRed: 0 green: 0 blue: 0 alpha: 0.35];
|
||||
|
||||
// Gradient Declarations
|
||||
NSArray* glossFillColors = [NSArray arrayWithObjects:
|
||||
(id)glossBottom.CGColor,
|
||||
(id)glossTop.CGColor, nil];
|
||||
CGFloat glossFillLocations[] = {0, 1};
|
||||
CGGradientRef glossFill = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)glossFillColors, glossFillLocations);
|
||||
|
||||
// Shadow Declarations
|
||||
UIColor* baseShadow = shadowBlack;
|
||||
CGSize baseShadowOffset = CGSizeMake(0.1, 6.1);
|
||||
CGFloat baseShadowBlurRadius = 6;
|
||||
UIColor* innerShadow = innerShadowColor;
|
||||
CGSize innerShadowOffset = CGSizeMake(0.1, 1.1);
|
||||
CGFloat innerShadowBlurRadius = 1;
|
||||
|
||||
CGFloat backgroundStrokeWidth = 1;
|
||||
CGFloat outerStrokeStrokeWidth = 1;
|
||||
|
||||
// Frames
|
||||
CGRect frame = rect;
|
||||
CGRect innerFrame = CGRectMake(frame.origin.x + backgroundStrokeWidth, frame.origin.y + backgroundStrokeWidth, frame.size.width - backgroundStrokeWidth * 2, frame.size.height - backgroundStrokeWidth * 2);
|
||||
CGRect glossFrame = CGRectMake(frame.origin.x - backgroundStrokeWidth / 2, frame.origin.y - backgroundStrokeWidth / 2, frame.size.width + backgroundStrokeWidth, frame.size.height / 2 + backgroundStrokeWidth + 0.5);
|
||||
|
||||
//// CoreGroup ////
|
||||
{
|
||||
CGContextSaveGState(context);
|
||||
CGContextSetAlpha(context, 0.83);
|
||||
CGContextBeginTransparencyLayer(context, NULL);
|
||||
|
||||
// Background Drawing
|
||||
UIBezierPath* backgroundPath = [UIBezierPath bezierPath];
|
||||
[backgroundPath moveToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + radius)];
|
||||
[backgroundPath addLineToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMaxY(frame) - radius)]; // left
|
||||
[backgroundPath addArcWithCenter:CGPointMake(CGRectGetMinX(frame) + radius, CGRectGetMaxY(frame) - radius) radius:radius startAngle:M_PI endAngle:M_PI / 2 clockwise:NO]; // bottom-left corner
|
||||
|
||||
// pointer down
|
||||
if (!pointingUp) {
|
||||
[backgroundPath addLineToPoint:CGPointMake(CGRectGetMinX(anchorRect), CGRectGetMaxY(frame))];
|
||||
[backgroundPath addLineToPoint:CGPointMake(CGRectGetMinX(anchorRect) + anchorRect.size.width / 2, CGRectGetMaxY(frame) + anchorRect.size.height)];
|
||||
[backgroundPath addLineToPoint:CGPointMake(CGRectGetMaxX(anchorRect), CGRectGetMaxY(frame))];
|
||||
}
|
||||
|
||||
[backgroundPath addLineToPoint:CGPointMake(CGRectGetMaxX(frame) - radius, CGRectGetMaxY(frame))]; // bottom
|
||||
[backgroundPath addArcWithCenter:CGPointMake(CGRectGetMaxX(frame) - radius, CGRectGetMaxY(frame) - radius) radius:radius startAngle:M_PI / 2 endAngle:0.0f clockwise:NO]; // bottom-right corner
|
||||
[backgroundPath addLineToPoint: CGPointMake(CGRectGetMaxX(frame), CGRectGetMinY(frame) + radius)]; // right
|
||||
[backgroundPath addArcWithCenter:CGPointMake(CGRectGetMaxX(frame) - radius, CGRectGetMinY(frame) + radius) radius:radius startAngle:0.0f endAngle:-M_PI / 2 clockwise:NO]; // top-right corner
|
||||
|
||||
// pointer up
|
||||
if (pointingUp) {
|
||||
[backgroundPath addLineToPoint:CGPointMake(CGRectGetMaxX(anchorRect), CGRectGetMinY(frame))];
|
||||
[backgroundPath addLineToPoint:CGPointMake(CGRectGetMinX(anchorRect) + anchorRect.size.width / 2, CGRectGetMinY(frame) - anchorRect.size.height)];
|
||||
[backgroundPath addLineToPoint:CGPointMake(CGRectGetMinX(anchorRect), CGRectGetMinY(frame))];
|
||||
}
|
||||
|
||||
[backgroundPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + radius, CGRectGetMinY(frame))]; // top
|
||||
[backgroundPath addArcWithCenter:CGPointMake(CGRectGetMinX(frame) + radius, CGRectGetMinY(frame) + radius) radius:radius startAngle:-M_PI / 2 endAngle:M_PI clockwise:NO]; // top-left corner
|
||||
[backgroundPath closePath];
|
||||
CGContextSaveGState(context);
|
||||
CGContextSetShadowWithColor(context, baseShadowOffset, baseShadowBlurRadius, baseShadow.CGColor);
|
||||
[fillBlack setFill];
|
||||
[backgroundPath fill];
|
||||
|
||||
// Background Inner Shadow
|
||||
CGRect backgroundBorderRect = CGRectInset([backgroundPath bounds], -innerShadowBlurRadius, -innerShadowBlurRadius);
|
||||
backgroundBorderRect = CGRectOffset(backgroundBorderRect, -innerShadowOffset.width, -innerShadowOffset.height);
|
||||
backgroundBorderRect = CGRectInset(CGRectUnion(backgroundBorderRect, [backgroundPath bounds]), -1, -1);
|
||||
|
||||
UIBezierPath* backgroundNegativePath = [UIBezierPath bezierPathWithRect: backgroundBorderRect];
|
||||
[backgroundNegativePath appendPath: backgroundPath];
|
||||
backgroundNegativePath.usesEvenOddFillRule = YES;
|
||||
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
CGFloat xOffset = innerShadowOffset.width + round(backgroundBorderRect.size.width);
|
||||
CGFloat yOffset = innerShadowOffset.height;
|
||||
CGContextSetShadowWithColor(context,
|
||||
CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),
|
||||
innerShadowBlurRadius,
|
||||
innerShadow.CGColor);
|
||||
|
||||
[backgroundPath addClip];
|
||||
CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(backgroundBorderRect.size.width), 0);
|
||||
[backgroundNegativePath applyTransform: transform];
|
||||
[[UIColor grayColor] setFill];
|
||||
[backgroundNegativePath fill];
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
[strokeColor setStroke];
|
||||
backgroundPath.lineWidth = backgroundStrokeWidth;
|
||||
[backgroundPath stroke];
|
||||
|
||||
|
||||
// Inner Stroke Drawing
|
||||
CGFloat innerRadius = radius - 1.0;
|
||||
CGRect anchorInnerRect = anchorRect;
|
||||
anchorInnerRect.origin.x += backgroundStrokeWidth / 2;
|
||||
anchorInnerRect.origin.y -= backgroundStrokeWidth / 2;
|
||||
anchorInnerRect.size.width -= backgroundStrokeWidth;
|
||||
anchorInnerRect.size.height -= backgroundStrokeWidth / 2;
|
||||
|
||||
UIBezierPath* innerStrokePath = [UIBezierPath bezierPath];
|
||||
[innerStrokePath moveToPoint:CGPointMake(CGRectGetMinX(innerFrame), CGRectGetMinY(innerFrame) + innerRadius)];
|
||||
[innerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(innerFrame), CGRectGetMaxY(innerFrame) - innerRadius)]; // left
|
||||
[innerStrokePath addArcWithCenter:CGPointMake(CGRectGetMinX(innerFrame) + innerRadius, CGRectGetMaxY(innerFrame) - innerRadius) radius:innerRadius startAngle:M_PI endAngle:M_PI / 2 clockwise:NO]; // bottom-left corner
|
||||
|
||||
// pointer down
|
||||
if (!pointingUp) {
|
||||
[innerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(anchorInnerRect), CGRectGetMaxY(innerFrame))];
|
||||
[innerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(anchorInnerRect) + anchorInnerRect.size.width / 2, CGRectGetMaxY(innerFrame) + anchorInnerRect.size.height)];
|
||||
[innerStrokePath addLineToPoint:CGPointMake(CGRectGetMaxX(anchorInnerRect), CGRectGetMaxY(innerFrame))];
|
||||
}
|
||||
|
||||
[innerStrokePath addLineToPoint:CGPointMake(CGRectGetMaxX(innerFrame) - innerRadius, CGRectGetMaxY(innerFrame))]; // bottom
|
||||
[innerStrokePath addArcWithCenter:CGPointMake(CGRectGetMaxX(innerFrame) - innerRadius, CGRectGetMaxY(innerFrame) - innerRadius) radius:innerRadius startAngle:M_PI / 2 endAngle:0.0f clockwise:NO]; // bottom-right corner
|
||||
[innerStrokePath addLineToPoint: CGPointMake(CGRectGetMaxX(innerFrame), CGRectGetMinY(innerFrame) + innerRadius)]; // right
|
||||
[innerStrokePath addArcWithCenter:CGPointMake(CGRectGetMaxX(innerFrame) - innerRadius, CGRectGetMinY(innerFrame) + innerRadius) radius:innerRadius startAngle:0.0f endAngle:-M_PI / 2 clockwise:NO]; // top-right corner
|
||||
|
||||
// pointer up
|
||||
if (pointingUp) {
|
||||
[innerStrokePath addLineToPoint:CGPointMake(CGRectGetMaxX(anchorInnerRect), CGRectGetMinY(innerFrame))];
|
||||
[innerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(anchorInnerRect) + anchorRect.size.width / 2, CGRectGetMinY(innerFrame) - anchorInnerRect.size.height)];
|
||||
[innerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(anchorInnerRect), CGRectGetMinY(innerFrame))];
|
||||
}
|
||||
|
||||
[innerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(innerFrame) + innerRadius, CGRectGetMinY(innerFrame))]; // top
|
||||
[innerStrokePath addArcWithCenter:CGPointMake(CGRectGetMinX(innerFrame) + innerRadius, CGRectGetMinY(innerFrame) + innerRadius) radius:innerRadius startAngle:-M_PI / 2 endAngle:M_PI clockwise:NO]; // top-left corner
|
||||
[innerStrokePath closePath];
|
||||
|
||||
[innerStrokeColor setStroke];
|
||||
innerStrokePath.lineWidth = backgroundStrokeWidth;
|
||||
[innerStrokePath stroke];
|
||||
|
||||
|
||||
//// GlossGroup ////
|
||||
{
|
||||
CGContextSaveGState(context);
|
||||
CGContextSetAlpha(context, 0.45);
|
||||
CGContextBeginTransparencyLayer(context, NULL);
|
||||
|
||||
CGFloat glossRadius = radius + 0.5;
|
||||
|
||||
// Gloss Drawing
|
||||
UIBezierPath* glossPath = [UIBezierPath bezierPath];
|
||||
[glossPath moveToPoint:CGPointMake(CGRectGetMinX(glossFrame), CGRectGetMinY(glossFrame))];
|
||||
[glossPath addLineToPoint:CGPointMake(CGRectGetMinX(glossFrame), CGRectGetMaxY(glossFrame) - glossRadius)]; // left
|
||||
[glossPath addArcWithCenter:CGPointMake(CGRectGetMinX(glossFrame) + glossRadius, CGRectGetMaxY(glossFrame) - glossRadius) radius:glossRadius startAngle:M_PI endAngle:M_PI / 2 clockwise:NO]; // bottom-left corner
|
||||
[glossPath addLineToPoint:CGPointMake(CGRectGetMaxX(glossFrame) - glossRadius, CGRectGetMaxY(glossFrame))]; // bottom
|
||||
[glossPath addArcWithCenter:CGPointMake(CGRectGetMaxX(glossFrame) - glossRadius, CGRectGetMaxY(glossFrame) - glossRadius) radius:glossRadius startAngle:M_PI / 2 endAngle:0.0f clockwise:NO]; // bottom-right corner
|
||||
[glossPath addLineToPoint: CGPointMake(CGRectGetMaxX(glossFrame), CGRectGetMinY(glossFrame) - glossRadius)]; // right
|
||||
[glossPath addArcWithCenter:CGPointMake(CGRectGetMaxX(glossFrame) - glossRadius, CGRectGetMinY(glossFrame) + glossRadius) radius:glossRadius startAngle:0.0f endAngle:-M_PI / 2 clockwise:NO]; // top-right corner
|
||||
|
||||
// pointer up
|
||||
if (pointingUp) {
|
||||
[glossPath addLineToPoint:CGPointMake(CGRectGetMaxX(anchorRect), CGRectGetMinY(glossFrame))];
|
||||
[glossPath addLineToPoint:CGPointMake(CGRectGetMinX(anchorRect) + roundf(anchorRect.size.width / 2), CGRectGetMinY(glossFrame) - anchorRect.size.height)];
|
||||
[glossPath addLineToPoint:CGPointMake(CGRectGetMinX(anchorRect), CGRectGetMinY(glossFrame))];
|
||||
}
|
||||
|
||||
[glossPath addLineToPoint:CGPointMake(CGRectGetMinX(glossFrame) + glossRadius, CGRectGetMinY(glossFrame))]; // top
|
||||
[glossPath addArcWithCenter:CGPointMake(CGRectGetMinX(glossFrame) + glossRadius, CGRectGetMinY(glossFrame) + glossRadius) radius:glossRadius startAngle:-M_PI / 2 endAngle:M_PI clockwise:NO]; // top-left corner
|
||||
[glossPath closePath];
|
||||
|
||||
CGContextSaveGState(context);
|
||||
[glossPath addClip];
|
||||
CGRect glossBounds = glossPath.bounds;
|
||||
CGContextDrawLinearGradient(context, glossFill,
|
||||
CGPointMake(CGRectGetMidX(glossBounds), CGRectGetMaxY(glossBounds)),
|
||||
CGPointMake(CGRectGetMidX(glossBounds), CGRectGetMinY(glossBounds)),
|
||||
0);
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
CGContextEndTransparencyLayer(context);
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
CGContextEndTransparencyLayer(context);
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
// Outer Stroke Drawing
|
||||
UIBezierPath* outerStrokePath = [UIBezierPath bezierPath];
|
||||
[outerStrokePath moveToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + radius)];
|
||||
[outerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMaxY(frame) - radius)]; // left
|
||||
[outerStrokePath addArcWithCenter:CGPointMake(CGRectGetMinX(frame) + radius, CGRectGetMaxY(frame) - radius) radius:radius startAngle:M_PI endAngle:M_PI / 2 clockwise:NO]; // bottom-left corner
|
||||
|
||||
// pointer down
|
||||
if (!pointingUp) {
|
||||
[outerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(anchorRect), CGRectGetMaxY(frame))];
|
||||
[outerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(anchorRect) + anchorRect.size.width / 2, CGRectGetMaxY(frame) + anchorRect.size.height)];
|
||||
[outerStrokePath addLineToPoint:CGPointMake(CGRectGetMaxX(anchorRect), CGRectGetMaxY(frame))];
|
||||
}
|
||||
|
||||
[outerStrokePath addLineToPoint:CGPointMake(CGRectGetMaxX(frame) - radius, CGRectGetMaxY(frame))]; // bottom
|
||||
[outerStrokePath addArcWithCenter:CGPointMake(CGRectGetMaxX(frame) - radius, CGRectGetMaxY(frame) - radius) radius:radius startAngle:M_PI / 2 endAngle:0.0f clockwise:NO]; // bottom-right corner
|
||||
[outerStrokePath addLineToPoint: CGPointMake(CGRectGetMaxX(frame), CGRectGetMinY(frame) + radius)]; // right
|
||||
[outerStrokePath addArcWithCenter:CGPointMake(CGRectGetMaxX(frame) - radius, CGRectGetMinY(frame) + radius) radius:radius startAngle:0.0f endAngle:-M_PI / 2 clockwise:NO]; // top-right corner
|
||||
|
||||
// pointer up
|
||||
if (pointingUp) {
|
||||
[outerStrokePath addLineToPoint:CGPointMake(CGRectGetMaxX(anchorRect), CGRectGetMinY(frame))];
|
||||
[outerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(anchorRect) + anchorRect.size.width / 2, CGRectGetMinY(frame) - anchorRect.size.height)];
|
||||
[outerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(anchorRect), CGRectGetMinY(frame))];
|
||||
}
|
||||
|
||||
[outerStrokePath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + radius, CGRectGetMinY(frame))]; // top
|
||||
[outerStrokePath addArcWithCenter:CGPointMake(CGRectGetMinX(frame) + radius, CGRectGetMinY(frame) + radius) radius:radius startAngle:-M_PI / 2 endAngle:M_PI clockwise:NO]; // top-left corner
|
||||
[outerStrokePath closePath];
|
||||
CGContextSaveGState(context);
|
||||
CGContextSetShadowWithColor(context, baseShadowOffset, baseShadowBlurRadius, baseShadow.CGColor);
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
[outerStrokeColor setStroke];
|
||||
outerStrokePath.lineWidth = outerStrokeStrokeWidth;
|
||||
[outerStrokePath stroke];
|
||||
|
||||
//// Cleanup
|
||||
CGGradientRelease(glossFill);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
}
|
||||
|
||||
@end
|
||||
@ -239,6 +239,185 @@ THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
## SMCalloutView
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
## SQLite.swift
|
||||
|
||||
(The MIT License)
|
||||
|
||||
@ -274,6 +274,191 @@ THE SOFTWARE.
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string> Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS</string>
|
||||
<key>License</key>
|
||||
<string>Apache License, Version 2.0</string>
|
||||
<key>Title</key>
|
||||
<string>SMCalloutView</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>(The MIT License)
|
||||
|
||||
@ -91,11 +91,13 @@ strip_invalid_archs() {
|
||||
if [[ "$CONFIGURATION" == "Debug" ]]; then
|
||||
install_framework "$BUILT_PRODUCTS_DIR/FileMD5Hash/FileMD5Hash.framework"
|
||||
install_framework "$BUILT_PRODUCTS_DIR/SDWebImage/SDWebImage.framework"
|
||||
install_framework "$BUILT_PRODUCTS_DIR/SMCalloutView/SMCalloutView.framework"
|
||||
install_framework "$BUILT_PRODUCTS_DIR/SQLite.swift/SQLite.framework"
|
||||
fi
|
||||
if [[ "$CONFIGURATION" == "Release" ]]; then
|
||||
install_framework "$BUILT_PRODUCTS_DIR/FileMD5Hash/FileMD5Hash.framework"
|
||||
install_framework "$BUILT_PRODUCTS_DIR/SDWebImage/SDWebImage.framework"
|
||||
install_framework "$BUILT_PRODUCTS_DIR/SMCalloutView/SMCalloutView.framework"
|
||||
install_framework "$BUILT_PRODUCTS_DIR/SQLite.swift/SQLite.framework"
|
||||
fi
|
||||
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage" "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift" "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage" "$PODS_CONFIGURATION_BUILD_DIR/SMCalloutView" "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift" "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Crashlytics" "${PODS_ROOT}/Headers/Public/Fabric"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash/FileMD5Hash.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift/SQLite.framework/Headers" -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/Crashlytics" -isystem "${PODS_ROOT}/Headers/Public/Fabric"
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"z" -framework "Crashlytics" -framework "Fabric" -framework "FileMD5Hash" -framework "SDWebImage" -framework "SQLite" -framework "Security" -framework "SystemConfiguration" -framework "UIKit"
|
||||
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash/FileMD5Hash.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SMCalloutView/SMCalloutView.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift/SQLite.framework/Headers" -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/Crashlytics" -isystem "${PODS_ROOT}/Headers/Public/Fabric"
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"z" -framework "Crashlytics" -framework "Fabric" -framework "FileMD5Hash" -framework "SDWebImage" -framework "SMCalloutView" -framework "SQLite" -framework "Security" -framework "SystemConfiguration" -framework "UIKit"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
PODS_BUILD_DIR = $BUILD_DIR
|
||||
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage" "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift" "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage" "$PODS_CONFIGURATION_BUILD_DIR/SMCalloutView" "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift" "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Crashlytics" "${PODS_ROOT}/Headers/Public/Fabric"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash/FileMD5Hash.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift/SQLite.framework/Headers" -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/Crashlytics" -isystem "${PODS_ROOT}/Headers/Public/Fabric"
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"z" -framework "Crashlytics" -framework "Fabric" -framework "FileMD5Hash" -framework "SDWebImage" -framework "SQLite" -framework "Security" -framework "SystemConfiguration" -framework "UIKit"
|
||||
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash/FileMD5Hash.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SMCalloutView/SMCalloutView.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift/SQLite.framework/Headers" -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/Crashlytics" -isystem "${PODS_ROOT}/Headers/Public/Fabric"
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"z" -framework "Crashlytics" -framework "Fabric" -framework "FileMD5Hash" -framework "SDWebImage" -framework "SMCalloutView" -framework "SQLite" -framework "Security" -framework "SystemConfiguration" -framework "UIKit"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
PODS_BUILD_DIR = $BUILD_DIR
|
||||
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
|
||||
26
Pods/Target Support Files/SMCalloutView/Info.plist
generated
Normal file
26
Pods/Target Support Files/SMCalloutView/Info.plist
generated
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.1.5</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
5
Pods/Target Support Files/SMCalloutView/SMCalloutView-dummy.m
generated
Normal file
5
Pods/Target Support Files/SMCalloutView/SMCalloutView-dummy.m
generated
Normal file
@ -0,0 +1,5 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_SMCalloutView : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_SMCalloutView
|
||||
@end
|
||||
12
Pods/Target Support Files/SMCalloutView/SMCalloutView-prefix.pch
generated
Normal file
12
Pods/Target Support Files/SMCalloutView/SMCalloutView-prefix.pch
generated
Normal file
@ -0,0 +1,12 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
18
Pods/Target Support Files/SMCalloutView/SMCalloutView-umbrella.h
generated
Normal file
18
Pods/Target Support Files/SMCalloutView/SMCalloutView-umbrella.h
generated
Normal file
@ -0,0 +1,18 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#import "SMCalloutView.h"
|
||||
#import "SMClassicCalloutView.h"
|
||||
|
||||
FOUNDATION_EXPORT double SMCalloutViewVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char SMCalloutViewVersionString[];
|
||||
|
||||
6
Pods/Target Support Files/SMCalloutView/SMCalloutView.modulemap
generated
Normal file
6
Pods/Target Support Files/SMCalloutView/SMCalloutView.modulemap
generated
Normal file
@ -0,0 +1,6 @@
|
||||
framework module SMCalloutView {
|
||||
umbrella header "SMCalloutView-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
||||
9
Pods/Target Support Files/SMCalloutView/SMCalloutView.xcconfig
generated
Normal file
9
Pods/Target Support Files/SMCalloutView/SMCalloutView.xcconfig
generated
Normal file
@ -0,0 +1,9 @@
|
||||
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/SMCalloutView
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Crashlytics" "${PODS_ROOT}/Headers/Public/Fabric"
|
||||
PODS_BUILD_DIR = $BUILD_DIR
|
||||
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SMCalloutView
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
Loading…
Reference in New Issue
Block a user