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 */; };
|
BF1173501DA32CF600047DF8 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */; };
|
||||||
BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7551D5D29B0000BB055 /* PreviewGameViewController.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 */; };
|
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 */; };
|
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */; };
|
||||||
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1DAD5C1D9F576000E752A7 /* SystemControllerSkinsViewController.swift */; };
|
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1DAD5C1D9F576000E752A7 /* SystemControllerSkinsViewController.swift */; };
|
||||||
BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2451BB73FE800CCF94A /* Assets.xcassets */; };
|
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 */; };
|
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34FA101CF1899D006624C7 /* CheatTextView.swift */; };
|
||||||
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF11C5D7FB000C1184C /* PauseViewController.swift */; };
|
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF11C5D7FB000C1184C /* PauseViewController.swift */; };
|
||||||
BF353FF61C5D837600C1184C /* PauseMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF353FF41C5D837600C1184C /* PauseMenu.storyboard */; };
|
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 */; };
|
BF353FFF1C5DA3C500C1184C /* PausePresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FFD1C5DA3C500C1184C /* PausePresentationController.swift */; };
|
||||||
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */; };
|
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */; };
|
||||||
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */; };
|
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 */; };
|
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */; };
|
||||||
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */; };
|
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */; };
|
||||||
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF5E7F451B9A652600AE44F8 /* Settings.storyboard */; };
|
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 */; };
|
BF6866171DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */; };
|
||||||
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF696B7F1D9B2B02009639E0 /* Theme.swift */; };
|
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF696B7F1D9B2B02009639E0 /* Theme.swift */; };
|
||||||
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3121EB7E47F008E83CD /* ImportOption.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 */; };
|
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, ); }; };
|
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 */; };
|
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 */; };
|
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 */; };
|
BF930FFD1EB6D6FF00E8DBA0 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF930FFC1EB6D6FF00E8DBA0 /* System.swift */; };
|
||||||
BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2761E4977BF0030E7AD /* GameMetadata.swift */; };
|
BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2761E4977BF0030E7AD /* GameMetadata.swift */; };
|
||||||
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.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 */; };
|
BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63BDE91D389EEB00FCB040 /* GameViewController.swift */; };
|
||||||
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FEC1B8AA4FA00495943 /* Settings.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 */; };
|
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 */; };
|
BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */; };
|
||||||
BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.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 */; };
|
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */; };
|
||||||
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDD04F01D5E2C27002D450E /* GameCollectionViewController.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 */; };
|
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */; };
|
||||||
BFEC732D1AAECC4A00650035 /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; };
|
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, ); }; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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; };
|
BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBCDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@ -262,6 +278,8 @@
|
|||||||
BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */,
|
BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */,
|
||||||
BFBAB2E21EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift */,
|
BFBAB2E21EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift */,
|
||||||
BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */,
|
BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */,
|
||||||
|
BFC6F7B71F435BC500221B96 /* Input+Display.swift */,
|
||||||
|
BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -270,6 +288,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */,
|
BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */,
|
||||||
|
BF616A121F08184A0077F8B2 /* ControllerInputsViewController.swift */,
|
||||||
|
BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */,
|
||||||
);
|
);
|
||||||
name = Controllers;
|
name = Controllers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -316,6 +336,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BF5942581E09BB810051894B /* Action.swift */,
|
BF5942581E09BB810051894B /* Action.swift */,
|
||||||
|
BFE0229C1F5B56840052D888 /* Popover Menu */,
|
||||||
BF5942671E09BBB70051894B /* Collection View */,
|
BF5942671E09BBB70051894B /* Collection View */,
|
||||||
BF5942601E09BBA80051894B /* Loading */,
|
BF5942601E09BBA80051894B /* Loading */,
|
||||||
);
|
);
|
||||||
@ -428,8 +449,8 @@
|
|||||||
children = (
|
children = (
|
||||||
BF353FF41C5D837600C1184C /* PauseMenu.storyboard */,
|
BF353FF41C5D837600C1184C /* PauseMenu.storyboard */,
|
||||||
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */,
|
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */,
|
||||||
BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */,
|
BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */,
|
||||||
BF353FF81C5D870B00C1184C /* PauseItem.swift */,
|
BF353FF81C5D870B00C1184C /* MenuItem.swift */,
|
||||||
BF3540031C5DA6D800C1184C /* Save States */,
|
BF3540031C5DA6D800C1184C /* Save States */,
|
||||||
BFC9B7371CEFCD08008629BB /* Cheats */,
|
BFC9B7371CEFCD08008629BB /* Cheats */,
|
||||||
BF353FFB1C5DA2F600C1184C /* Presentation Controller */,
|
BF353FFB1C5DA2F600C1184C /* Presentation Controller */,
|
||||||
@ -508,6 +529,16 @@
|
|||||||
name = Cheats;
|
name = Cheats;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
BFE0229C1F5B56840052D888 /* Popover Menu */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */,
|
||||||
|
BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */,
|
||||||
|
BF6424821F5B8F3F00D6AB44 /* ListMenuViewController.swift */,
|
||||||
|
);
|
||||||
|
name = "Popover Menu";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
BFEC732F1AAECCBD00650035 /* Resources */ = {
|
BFEC732F1AAECCBD00650035 /* Resources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -521,8 +552,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BFFA71D91AAC406100EE9DD1 /* Delta */,
|
BFFA71D91AAC406100EE9DD1 /* Delta */,
|
||||||
BFEC732F1AAECCBD00650035 /* Resources */,
|
|
||||||
BF9F4FCD1AAD7B25004C9500 /* Frameworks */,
|
BF9F4FCD1AAD7B25004C9500 /* Frameworks */,
|
||||||
|
BFEC732F1AAECCBD00650035 /* Resources */,
|
||||||
BFFA71D81AAC406100EE9DD1 /* Products */,
|
BFFA71D81AAC406100EE9DD1 /* Products */,
|
||||||
FD1E8AE87FA2DB8793F7B937 /* Pods */,
|
FD1E8AE87FA2DB8793F7B937 /* Pods */,
|
||||||
);
|
);
|
||||||
@ -572,6 +603,7 @@
|
|||||||
children = (
|
children = (
|
||||||
BF63BDE91D389EEB00FCB040 /* GameViewController.swift */,
|
BF63BDE91D389EEB00FCB040 /* GameViewController.swift */,
|
||||||
BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */,
|
BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */,
|
||||||
|
BF15AF831F54B43B009B6AAB /* ActionInput.swift */,
|
||||||
);
|
);
|
||||||
path = Emulation;
|
path = Emulation;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -776,6 +808,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
BFC6F7B81F435BC500221B96 /* Input+Display.swift in Sources */,
|
||||||
BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */,
|
BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */,
|
||||||
BF6BF3181EB82111008E83CD /* iTunesImportOption.swift in Sources */,
|
BF6BF3181EB82111008E83CD /* iTunesImportOption.swift in Sources */,
|
||||||
BF59427C1E09BC830051894B /* Cheat.swift in Sources */,
|
BF59427C1E09BC830051894B /* Cheat.swift in Sources */,
|
||||||
@ -789,24 +822,29 @@
|
|||||||
BF59428E1E09BCFB0051894B /* ImportController.swift in Sources */,
|
BF59428E1E09BCFB0051894B /* ImportController.swift in Sources */,
|
||||||
BF930FFD1EB6D6FF00E8DBA0 /* System.swift in Sources */,
|
BF930FFD1EB6D6FF00E8DBA0 /* System.swift in Sources */,
|
||||||
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */,
|
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */,
|
||||||
|
BF6424831F5B8F3F00D6AB44 /* ListMenuViewController.swift in Sources */,
|
||||||
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */,
|
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */,
|
||||||
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */,
|
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */,
|
||||||
BFFC46201D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift in Sources */,
|
BFFC46201D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift in Sources */,
|
||||||
BF5942731E09BC700051894B /* Model.xcdatamodel in Sources */,
|
BF5942731E09BC700051894B /* Model.xcdatamodel in Sources */,
|
||||||
|
BF15AF841F54B43B009B6AAB /* ActionInput.swift in Sources */,
|
||||||
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.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 */,
|
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */,
|
||||||
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
|
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
|
||||||
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */,
|
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */,
|
||||||
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
|
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
|
||||||
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */,
|
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */,
|
||||||
|
BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */,
|
||||||
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */,
|
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */,
|
||||||
BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */,
|
BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */,
|
||||||
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
|
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
|
||||||
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */,
|
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */,
|
||||||
|
BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */,
|
||||||
BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */,
|
BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */,
|
||||||
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */,
|
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */,
|
||||||
BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */,
|
BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */,
|
||||||
|
BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */,
|
||||||
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */,
|
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */,
|
||||||
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */,
|
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */,
|
||||||
BF5942881E09BC8B0051894B /* _Game.swift in Sources */,
|
BF5942881E09BC8B0051894B /* _Game.swift in Sources */,
|
||||||
@ -819,6 +857,7 @@
|
|||||||
BF5942941E09BD1A0051894B /* NSManagedObject+Conveniences.swift in Sources */,
|
BF5942941E09BD1A0051894B /* NSManagedObject+Conveniences.swift in Sources */,
|
||||||
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */,
|
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */,
|
||||||
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
|
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
|
||||||
|
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */,
|
||||||
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */,
|
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */,
|
||||||
BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */,
|
BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */,
|
||||||
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */,
|
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */,
|
||||||
@ -835,13 +874,14 @@
|
|||||||
BF99A5971DC2F9C400468E9E /* ControllerSkinTableViewCell.swift in Sources */,
|
BF99A5971DC2F9C400468E9E /* ControllerSkinTableViewCell.swift in Sources */,
|
||||||
BF5942861E09BC8B0051894B /* _Cheat.swift in Sources */,
|
BF5942861E09BC8B0051894B /* _Cheat.swift in Sources */,
|
||||||
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */,
|
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */,
|
||||||
|
BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */,
|
||||||
BF5942931E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m in Sources */,
|
BF5942931E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m in Sources */,
|
||||||
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */,
|
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */,
|
||||||
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */,
|
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */,
|
||||||
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */,
|
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */,
|
||||||
BF59425C1E09BB810051894B /* Action.swift in Sources */,
|
BF59425C1E09BB810051894B /* Action.swift in Sources */,
|
||||||
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */,
|
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */,
|
||||||
BF7AE8081C2E858400B1B5BC /* PauseMenuViewController.swift in Sources */,
|
BF7AE8081C2E858400B1B5BC /* GridMenuViewController.swift in Sources */,
|
||||||
BF6BF31A1EB82146008E83CD /* ClipboardImportOption.swift in Sources */,
|
BF6BF31A1EB82146008E83CD /* ClipboardImportOption.swift in Sources */,
|
||||||
BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */,
|
BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */,
|
||||||
BF59427F1E09BC830051894B /* GameCollection.swift in Sources */,
|
BF59427F1E09BC830051894B /* GameCollection.swift in Sources */,
|
||||||
|
|||||||
@ -40,7 +40,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Controllers
|
// Controllers
|
||||||
ExternalControllerManager.shared.startMonitoringExternalControllers()
|
ExternalGameControllerManager.shared.startMonitoring()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<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">
|
<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>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.16"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11151.4"/>
|
|
||||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
<customFonts key="customFonts">
|
||||||
|
<array key="Menlo.ttc">
|
||||||
|
<string>Menlo-Regular</string>
|
||||||
|
</array>
|
||||||
|
</customFonts>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--Pause View Controller-->
|
<!--Pause View Controller-->
|
||||||
<scene sceneID="Wst-Dv-TjM">
|
<scene sceneID="Wst-Dv-TjM">
|
||||||
@ -16,15 +20,17 @@
|
|||||||
<viewControllerLayoutGuide type="bottom" id="gF0-0U-kR7"/>
|
<viewControllerLayoutGuide type="bottom" id="gF0-0U-kR7"/>
|
||||||
</layoutGuides>
|
</layoutGuides>
|
||||||
<view key="view" contentMode="scaleToFill" id="oOH-ea-jcb">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="p2M-dE-BJs" userLabel="Blur View">
|
<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">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rqN-NB-jbb">
|
<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>
|
<connections>
|
||||||
<segue destination="sWv-Ky-VGs" kind="embed" identifier="embedNavigationController" id="1Ja-XW-uoT"/>
|
<segue destination="sWv-Ky-VGs" kind="embed" identifier="embedNavigationController" id="1Ja-XW-uoT"/>
|
||||||
</connections>
|
</connections>
|
||||||
@ -63,7 +69,7 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<navigationController id="sWv-Ky-VGs" sceneMemberID="viewController">
|
<navigationController id="sWv-Ky-VGs" sceneMemberID="viewController">
|
||||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" barStyle="black" prompted="NO"/>
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
@ -78,9 +84,9 @@
|
|||||||
<!--Paused-->
|
<!--Paused-->
|
||||||
<scene sceneID="1md-hu-g0J">
|
<scene sceneID="1md-hu-g0J">
|
||||||
<objects>
|
<objects>
|
||||||
<collectionViewController id="0jA-NY-mvB" customClass="PauseMenuViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
<collectionViewController id="0jA-NY-mvB" customClass="GridMenuViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" delaysContentTouches="NO" dataMode="prototypes" id="scc-uc-vaJ">
|
<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="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<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"/>
|
<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">
|
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="10" id="yXv-zl-idO" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
|
||||||
@ -91,7 +97,7 @@
|
|||||||
</collectionViewFlowLayout>
|
</collectionViewFlowLayout>
|
||||||
<cells>
|
<cells>
|
||||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="6XS-Ne-nGZ" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="60" height="80"/>
|
<rect key="frame" x="0.0" y="0.0" width="60" height="80"/>
|
||||||
@ -128,7 +134,7 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<collectionViewController storyboardIdentifier="saveStatesViewController" id="OOk-k7-INg" customClass="SaveStatesViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
<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">
|
<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"/>
|
<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"/>
|
<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">
|
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="tvW-q1-PD8" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
|
||||||
@ -139,7 +145,7 @@
|
|||||||
</collectionViewFlowLayout>
|
</collectionViewFlowLayout>
|
||||||
<cells>
|
<cells>
|
||||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="c3N-1A-ryV" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||||
@ -148,7 +154,7 @@
|
|||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Header" id="YeY-W9-CC6" customClass="SaveStatesCollectionHeaderView" customModule="Delta" customModuleProvider="target">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</collectionReusableView>
|
</collectionReusableView>
|
||||||
<connections>
|
<connections>
|
||||||
@ -174,22 +180,22 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<tableViewController id="wb8-5o-1jE" customClass="CheatsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
<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">
|
<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"/>
|
<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"/>
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<prototypes>
|
<prototypes>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="e8g-ZW-5lQ" customClass="CheatTableViewCell" customModule="Delta" customModuleProvider="target">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="e8g-ZW-5lQ" id="AHE-Jk-ULE">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<visualEffectView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="emc-gw-KkJ" userLabel="Selected Background View">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" id="9bA-Tg-Bko">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
</view>
|
</view>
|
||||||
@ -198,8 +204,9 @@
|
|||||||
</vibrancyEffect>
|
</vibrancyEffect>
|
||||||
</visualEffectView>
|
</visualEffectView>
|
||||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="R4A-9d-DGb">
|
<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">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
</view>
|
</view>
|
||||||
<vibrancyEffect>
|
<vibrancyEffect>
|
||||||
@ -245,20 +252,21 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<tableViewController storyboardIdentifier="editCheatViewController" id="jTR-Oe-YUJ" customClass="EditCheatViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
<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">
|
<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"/>
|
<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"/>
|
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<sections>
|
<sections>
|
||||||
<tableViewSection headerTitle="Name" id="QT6-DZ-g70">
|
<tableViewSection headerTitle="Name" id="QT6-DZ-g70">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="ZeC-rg-QFa">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="ZeC-rg-QFa" id="UIF-fK-ApW">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<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">
|
<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"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<textInputTraits key="textInputTraits" autocapitalizationType="words" autocorrectionType="no" returnKeyType="done"/>
|
<textInputTraits key="textInputTraits" autocapitalizationType="words" autocorrectionType="no" returnKeyType="done"/>
|
||||||
<connections>
|
<connections>
|
||||||
@ -280,13 +288,14 @@
|
|||||||
<tableViewSection headerTitle="Type" footerTitle="Description" id="rvn-VK-2uH">
|
<tableViewSection headerTitle="Type" footerTitle="Description" id="rvn-VK-2uH">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Tst-zn-e04">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="Tst-zn-e04" id="gwV-zS-RWQ">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="xrD-ue-96Q">
|
<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>
|
<segments>
|
||||||
<segment title="First"/>
|
<segment title="First"/>
|
||||||
<segment title="Second"/>
|
<segment title="Second"/>
|
||||||
@ -308,13 +317,14 @@
|
|||||||
<tableViewSection headerTitle="Code" footerTitle="Description" id="rHC-nA-ga0">
|
<tableViewSection headerTitle="Code" footerTitle="Description" id="rHC-nA-ga0">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="210" id="xxc-cz-sb7">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="xxc-cz-sb7" id="agU-SE-fNa">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" text="XXXXXXXX YYYYYYYY" translatesAutoresizingMaskIntoConstraints="NO" id="a17-LB-QXD" customClass="CheatTextView" customModule="Delta" customModuleProvider="target">
|
<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"/>
|
<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"/>
|
<fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="26"/>
|
||||||
<textInputTraits key="textInputTraits" autocapitalizationType="allCharacters" autocorrectionType="no" spellCheckingType="no" returnKeyType="done"/>
|
<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)
|
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]?
|
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
|
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
|
class GameViewController: DeltaCore.GameViewController
|
||||||
@ -79,10 +95,8 @@ class GameViewController: DeltaCore.GameViewController
|
|||||||
fileprivate var context = CIContext(options: [kCIContextWorkingColorSpace: NSNull()])
|
fileprivate var context = CIContext(options: [kCIContextWorkingColorSpace: NSNull()])
|
||||||
|
|
||||||
// Sustain Buttons
|
// Sustain Buttons
|
||||||
fileprivate var updateSemaphores = Set<DispatchSemaphore>()
|
fileprivate var isSelectingSustainedButtons = false
|
||||||
fileprivate var sustainedInputs = [ObjectIdentifier: [Input]]()
|
fileprivate var sustainInputsMapping: SustainInputsMapping?
|
||||||
fileprivate var reactivateSustainedInputsQueue: OperationQueue
|
|
||||||
fileprivate var selectingSustainedButtons = false
|
|
||||||
|
|
||||||
fileprivate var sustainButtonsContentView: UIView!
|
fileprivate var sustainButtonsContentView: UIView!
|
||||||
fileprivate var sustainButtonsBlurView: UIVisualEffectView!
|
fileprivate var sustainButtonsBlurView: UIVisualEffectView!
|
||||||
@ -90,9 +104,6 @@ class GameViewController: DeltaCore.GameViewController
|
|||||||
|
|
||||||
required init()
|
required init()
|
||||||
{
|
{
|
||||||
self.reactivateSustainedInputsQueue = OperationQueue()
|
|
||||||
self.reactivateSustainedInputsQueue.maxConcurrentOperationCount = 1
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.initialize()
|
self.initialize()
|
||||||
@ -100,9 +111,6 @@ class GameViewController: DeltaCore.GameViewController
|
|||||||
|
|
||||||
required init?(coder aDecoder: NSCoder)
|
required init?(coder aDecoder: NSCoder)
|
||||||
{
|
{
|
||||||
self.reactivateSustainedInputsQueue = OperationQueue()
|
|
||||||
self.reactivateSustainedInputsQueue.maxConcurrentOperationCount = 1
|
|
||||||
|
|
||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
self.initialize()
|
self.initialize()
|
||||||
@ -112,8 +120,8 @@ class GameViewController: DeltaCore.GameViewController
|
|||||||
{
|
{
|
||||||
self.delegate = self
|
self.delegate = self
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalControllerDidConnect, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalGameControllerDidConnect, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalControllerDidDisconnect, 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.didEnterBackground(with:)), name: .UIApplicationDidEnterBackground, object: UIApplication.shared)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.settingsDidChange(with:)), name: .settingsDidChange, object: nil)
|
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)
|
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)
|
gameController.sustain(input)
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,22 +246,28 @@ extension GameViewController
|
|||||||
pauseViewController.saveStatesViewControllerDelegate = self
|
pauseViewController.saveStatesViewControllerDelegate = self
|
||||||
pauseViewController.cheatsViewControllerDelegate = 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
|
pauseViewController.fastForwardItem?.action = { [unowned self] item in
|
||||||
guard let emulatorCore = self.emulatorCore else { return }
|
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
|
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()
|
self.showSustainButtonView()
|
||||||
pauseViewController.dismiss()
|
pauseViewController.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-set gameController as pausingGameController.
|
||||||
|
self.pausingGameController = gameController
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pauseViewController = pauseViewController
|
self.pauseViewController = pauseViewController
|
||||||
@ -346,11 +355,7 @@ private extension GameViewController
|
|||||||
{
|
{
|
||||||
@objc func updateControllers()
|
@objc func updateControllers()
|
||||||
{
|
{
|
||||||
var controllers = [GameController]()
|
let controllers = [self.controllerView as GameController] + ExternalGameControllerManager.shared.connectedControllers
|
||||||
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 })
|
|
||||||
|
|
||||||
if let index = Settings.localControllerPlayerIndex
|
if let index = Settings.localControllerPlayerIndex
|
||||||
{
|
{
|
||||||
@ -590,17 +595,6 @@ extension GameViewController: SaveStatesViewControllerDelegate
|
|||||||
print(error)
|
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()
|
self.pauseViewController?.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -625,7 +619,12 @@ private extension GameViewController
|
|||||||
{
|
{
|
||||||
func showSustainButtonView()
|
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
|
let blurEffect = self.sustainButtonsBlurView.effect
|
||||||
self.sustainButtonsBlurView.effect = nil
|
self.sustainButtonsBlurView.effect = nil
|
||||||
@ -640,7 +639,18 @@ private extension GameViewController
|
|||||||
|
|
||||||
func hideSustainButtonView()
|
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
|
let blurEffect = self.sustainButtonsBlurView.effect
|
||||||
|
|
||||||
@ -652,96 +662,6 @@ private extension GameViewController
|
|||||||
self.sustainButtonsBlurView.effect = blurEffect
|
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 -
|
//MARK: - GameViewControllerDelegate -
|
||||||
@ -750,12 +670,17 @@ extension GameViewController: GameViewControllerDelegate
|
|||||||
{
|
{
|
||||||
func gameViewController(_ gameViewController: DeltaCore.GameViewController, handleMenuInputFrom gameController: GameController)
|
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()
|
self.hideSustainButtonView()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let pauseViewController = self.pauseViewController, !self.selectingSustainedButtons
|
if let pauseViewController = self.pauseViewController, !self.isSelectingSustainedButtons
|
||||||
{
|
{
|
||||||
pauseViewController.dismiss()
|
pauseViewController.dismiss()
|
||||||
}
|
}
|
||||||
@ -768,15 +693,7 @@ extension GameViewController: GameViewControllerDelegate
|
|||||||
|
|
||||||
func gameViewControllerShouldResumeEmulation(_ gameViewController: DeltaCore.GameViewController) -> Bool
|
func gameViewControllerShouldResumeEmulation(_ gameViewController: DeltaCore.GameViewController) -> Bool
|
||||||
{
|
{
|
||||||
return (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.selectingSustainedButtons && self.view.window != nil
|
return (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.isSelectingSustainedButtons && self.view.window != nil
|
||||||
}
|
|
||||||
|
|
||||||
func gameViewControllerDidUpdate(_ gameViewController: DeltaCore.GameViewController)
|
|
||||||
{
|
|
||||||
for semaphore in self.updateSemaphores
|
|
||||||
{
|
|
||||||
semaphore.signal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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
|
// Delta
|
||||||
//
|
//
|
||||||
// Created by Riley Testut on 12/21/15.
|
// Created by Riley Testut on 12/21/15.
|
||||||
@ -9,39 +9,57 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class PauseMenuViewController: UICollectionViewController
|
class GridMenuViewController: UICollectionViewController
|
||||||
{
|
{
|
||||||
var items = [PauseItem]() {
|
var items: [MenuItem] {
|
||||||
didSet
|
get { return self.dataSource.items }
|
||||||
{
|
set { self.dataSource.items = newValue; self.updateItems() }
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dataSource.items = self.items
|
var isVibrancyEnabled = true
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override var preferredContentSize: CGSize {
|
override var preferredContentSize: CGSize {
|
||||||
set { }
|
set { }
|
||||||
get { return self.collectionView?.contentSize ?? CGSize.zero }
|
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 prototypeCell = GridCollectionViewCell()
|
||||||
fileprivate var previousIndexPath: IndexPath? = nil
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder)
|
||||||
|
{
|
||||||
|
super.init(coder: aDecoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit
|
||||||
|
{
|
||||||
|
// Crashes on iOS 10 if not explicitly invalidated.
|
||||||
|
self.registeredKVOObservers.forEach { $0.invalidate() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PauseMenuViewController
|
extension GridMenuViewController
|
||||||
{
|
{
|
||||||
override func viewDidLoad()
|
override func viewDidLoad()
|
||||||
{
|
{
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
self.collectionView?.register(GridCollectionViewCell.self, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier)
|
||||||
|
|
||||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
|
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
|
||||||
self.configure(cell as! GridCollectionViewCell, for: indexPath)
|
self.configure(cell as! GridCollectionViewCell, for: indexPath)
|
||||||
}
|
}
|
||||||
@ -62,55 +80,66 @@ extension PauseMenuViewController
|
|||||||
if let indexPath = self.previousIndexPath
|
if let indexPath = self.previousIndexPath
|
||||||
{
|
{
|
||||||
UIView.animate(withDuration: 0.2) {
|
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)
|
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.maximumImageSize = CGSize(width: 60, height: 60)
|
||||||
|
|
||||||
cell.imageView.image = pauseItem.image
|
cell.imageView.image = pauseItem.image
|
||||||
cell.imageView.contentMode = .center
|
cell.imageView.contentMode = .center
|
||||||
cell.imageView.layer.borderWidth = 2
|
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.imageView.layer.cornerRadius = 10
|
||||||
|
|
||||||
cell.textLabel.text = pauseItem.text
|
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.tintColor = UIColor.black
|
||||||
cell.imageView.backgroundColor = UIColor.white
|
cell.imageView.backgroundColor = self.view.tintColor
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cell.imageView.tintColor = UIColor.white
|
cell.imageView.tintColor = self.view.tintColor
|
||||||
cell.imageView.backgroundColor = UIColor.clear
|
cell.imageView.backgroundColor = UIColor.clear
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.isImageViewVibrancyEnabled = true
|
cell.isImageViewVibrancyEnabled = self.isVibrancyEnabled
|
||||||
cell.isTextLabelVibrancyEnabled = true
|
cell.isTextLabelVibrancyEnabled = self.isVibrancyEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleSelectedStateForPauseItemAtIndexPath(_ indexPath: IndexPath)
|
func updateItems()
|
||||||
{
|
{
|
||||||
let pauseItem = self.items[indexPath.item]
|
self.registeredKVOObservers.removeAll()
|
||||||
pauseItem.selected = !pauseItem.selected
|
|
||||||
|
|
||||||
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.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
|
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)
|
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)
|
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)
|
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||||
{
|
{
|
||||||
self.previousIndexPath = indexPath
|
self.previousIndexPath = indexPath
|
||||||
|
|
||||||
self.toggleSelectedStateForPauseItemAtIndexPath(indexPath)
|
let item = self.items[indexPath.item]
|
||||||
|
item.isSelected = !item.isSelected
|
||||||
let pauseItem = self.items[indexPath.item]
|
item.action(item)
|
||||||
pauseItem.action(pauseItem)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 }
|
return [self.saveStateItem, self.loadStateItem, self.cheatCodesItem, self.fastForwardItem, self.sustainButtonsItem].flatMap { $0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pause Items
|
/// Pause Items
|
||||||
var saveStateItem: PauseItem?
|
var saveStateItem: MenuItem?
|
||||||
var loadStateItem: PauseItem?
|
var loadStateItem: MenuItem?
|
||||||
var cheatCodesItem: PauseItem?
|
var cheatCodesItem: MenuItem?
|
||||||
var fastForwardItem: PauseItem?
|
var fastForwardItem: MenuItem?
|
||||||
var sustainButtonsItem: PauseItem?
|
var sustainButtonsItem: MenuItem?
|
||||||
|
|
||||||
/// PauseInfoProviding
|
/// PauseInfoProviding
|
||||||
var pauseText: String?
|
var pauseText: String?
|
||||||
@ -85,8 +85,8 @@ extension PauseViewController
|
|||||||
self.pauseNavigationController.navigationBar.tintColor = UIColor.deltaPurple
|
self.pauseNavigationController.navigationBar.tintColor = UIColor.deltaPurple
|
||||||
self.pauseNavigationController.view.backgroundColor = UIColor.clear
|
self.pauseNavigationController.view.backgroundColor = UIColor.clear
|
||||||
|
|
||||||
let pauseMenuViewController = self.pauseNavigationController.topViewController as! PauseMenuViewController
|
let gridMenuViewController = self.pauseNavigationController.topViewController as! GridMenuViewController
|
||||||
pauseMenuViewController.items = self.pauseItems
|
gridMenuViewController.items = self.pauseItems
|
||||||
|
|
||||||
case "saveStates":
|
case "saveStates":
|
||||||
let saveStatesViewController = segue.destination as! SaveStatesViewController
|
let saveStatesViewController = segue.destination as! SaveStatesViewController
|
||||||
@ -135,21 +135,21 @@ private extension PauseViewController
|
|||||||
|
|
||||||
guard self.emulatorCore != nil else { return }
|
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.saveStatesViewControllerMode = .saving
|
||||||
self.performSegue(withIdentifier: "saveStates", sender: self)
|
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.saveStatesViewControllerMode = .loading
|
||||||
self.performSegue(withIdentifier: "saveStates", sender: self)
|
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.performSegue(withIdentifier: "cheats", sender: self)
|
||||||
})
|
})
|
||||||
|
|
||||||
self.fastForwardItem = PauseItem(image: #imageLiteral(resourceName: "FastForward"), text: NSLocalizedString("Fast Forward", comment: ""), action: { _ in })
|
self.fastForwardItem = MenuItem(text: NSLocalizedString("Fast Forward", comment: ""), image: #imageLiteral(resourceName: "FastForward"), action: { _ in })
|
||||||
self.sustainButtonsItem = PauseItem(image: #imageLiteral(resourceName: "SustainButtons"), text: NSLocalizedString("Sustain Buttons", comment: ""), 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.modalPresentationStyle = .custom
|
||||||
self.destination.modalPresentationCapturesStatusBarAppearance = true
|
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:)
|
// We need to force layout of destinationViewController.view _before_ animateTransition(using:)
|
||||||
// Otherwise, we'll get "Unable to simultaneously satisfy constraints" errors
|
// Otherwise, we'll get "Unable to simultaneously satisfy constraints" errors
|
||||||
self.destination.view.frame = self.source.view.frame
|
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 UIKit
|
||||||
import DeltaCore
|
import DeltaCore
|
||||||
|
|
||||||
private enum ControllersSettingsSection: Int
|
import Roxas
|
||||||
|
|
||||||
|
extension ControllersSettingsViewController
|
||||||
{
|
{
|
||||||
|
fileprivate enum Section: Int
|
||||||
|
{
|
||||||
case none
|
case none
|
||||||
case localDevice
|
case localDevice
|
||||||
case externalControllers
|
case externalControllers
|
||||||
|
case customizeControls
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LocalDeviceController: ExternalController
|
|
||||||
|
private class LocalDeviceController: NSObject, GameController
|
||||||
{
|
{
|
||||||
override var name: String {
|
var name: String {
|
||||||
return UIDevice.current.name
|
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
|
class ControllersSettingsViewController: UITableViewController
|
||||||
{
|
{
|
||||||
var playerIndex: Int? {
|
var playerIndex: Int! {
|
||||||
didSet
|
didSet {
|
||||||
{
|
self.title = NSLocalizedString("Player \(self.playerIndex + 1)", comment: "")
|
||||||
if let playerIndex = self.playerIndex
|
|
||||||
{
|
|
||||||
self.title = NSLocalizedString("Player \(playerIndex + 1)", comment: "")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.title = NSLocalizedString("Controllers", 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 = {
|
fileprivate lazy var localDeviceController: LocalDeviceController = {
|
||||||
let device = LocalDeviceController()
|
let device = LocalDeviceController()
|
||||||
@ -52,146 +67,76 @@ class ControllersSettingsViewController: UITableViewController
|
|||||||
{
|
{
|
||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalControllerDidConnect(_:)), name: .externalControllerDidConnect, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalGameControllerDidConnect(_:)), name: .externalGameControllerDidConnect, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalControllerDidDisconnect(_:)), name: .externalControllerDidDisconnect, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalGameControllerDidDisconnect(_:)), name: .externalGameControllerDidDisconnect, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad()
|
override func viewDidLoad()
|
||||||
{
|
{
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
}
|
|
||||||
|
|
||||||
//MARK: - Storyboards -
|
let gameControllers = [self.localDeviceController as GameController] + self.connectedControllers
|
||||||
|
for gameController in gameControllers
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
|
||||||
{
|
{
|
||||||
guard let indexPath = self.tableView.indexPathForSelectedRow else { return }
|
if gameController.playerIndex == self.playerIndex
|
||||||
|
|
||||||
var controllers = self.connectedControllers
|
|
||||||
controllers.append(self.localDeviceController)
|
|
||||||
|
|
||||||
// Reset previous controller
|
|
||||||
if let playerIndex = self.playerIndex, let index = controllers.index(where: { $0.playerIndex == playerIndex })
|
|
||||||
{
|
{
|
||||||
let controller = controllers[index]
|
self.gameController = gameController
|
||||||
controller.playerIndex = nil
|
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
|
extension ControllersSettingsViewController
|
||||||
{
|
{
|
||||||
override func numberOfSections(in tableView: UITableView) -> Int
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||||
{
|
{
|
||||||
if self.connectedControllers.count == 0
|
guard let identifier = segue.identifier else { return }
|
||||||
{
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
default: break
|
||||||
{
|
|
||||||
switch ControllersSettingsSection(rawValue: section)!
|
|
||||||
{
|
|
||||||
case .none: return 1
|
|
||||||
case .localDevice: return 1
|
|
||||||
case .externalControllers: return self.connectedControllers.count
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.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: "")
|
cell.textLabel?.text = NSLocalizedString("None", comment: "")
|
||||||
|
|
||||||
if Settings.localControllerPlayerIndex != self.playerIndex && !self.connectedControllers.contains(where: { $0.playerIndex == self.playerIndex })
|
if Settings.localControllerPlayerIndex != self.playerIndex && !self.connectedControllers.contains(where: { $0.playerIndex == self.playerIndex })
|
||||||
{
|
{
|
||||||
cell.accessoryType = .checkmark
|
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
|
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
|
else
|
||||||
{
|
{
|
||||||
@ -210,20 +155,177 @@ extension ControllersSettingsViewController
|
|||||||
{
|
{
|
||||||
cell.detailTextLabel?.text = NSLocalizedString("Player \(playerIndex + 1)", comment: "")
|
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
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
|
||||||
{
|
{
|
||||||
switch ControllersSettingsSection(rawValue: section)!
|
switch Section(rawValue: section)!
|
||||||
{
|
{
|
||||||
case .none: return nil
|
case .none: return nil
|
||||||
case .localDevice: return NSLocalizedString("Local Device", comment: "")
|
case .localDevice: return NSLocalizedString("Local Device", comment: "")
|
||||||
case .externalControllers: return self.connectedControllers.count > 0 ? NSLocalizedString("External Controllers", 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"?>
|
<?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">
|
<device id="retina4_7" orientation="portrait">
|
||||||
<adaptation id="fullscreen"/>
|
<adaptation id="fullscreen"/>
|
||||||
</device>
|
</device>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<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="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@ -29,7 +29,7 @@
|
|||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="tls-Hv-Rx2">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
<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"/>
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="e3u-x9-IEC">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
<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"/>
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 3" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Cdn-11-xZe">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
<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"/>
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 4" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hls-3b-EaS">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
<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"/>
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="System Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="mBC-YU-BVK">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<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"/>
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="System Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Dxs-Me-IVU">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<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"/>
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="System Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="o9x-Kn-6bC">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<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"/>
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="whi-If-wFf">
|
<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>
|
<connections>
|
||||||
<action selector="beginChangingControllerOpacityWith:" destination="eHi-aO-uGS" eventType="touchDown" id="NG9-FX-62d"/>
|
<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"/>
|
<action selector="changeControllerOpacityWith:" destination="eHi-aO-uGS" eventType="valueChanged" id="Zci-tN-4uU"/>
|
||||||
@ -194,7 +194,7 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</slider>
|
</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">
|
<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>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="21" id="ACD-qY-k0J"/>
|
<constraint firstAttribute="height" constant="21" id="ACD-qY-k0J"/>
|
||||||
<constraint firstAttribute="width" constant="46" id="ZVd-ie-qRm"/>
|
<constraint firstAttribute="width" constant="46" id="ZVd-ie-qRm"/>
|
||||||
@ -240,6 +240,78 @@
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="1555" y="471"/>
|
<point key="canvasLocation" x="1555" y="471"/>
|
||||||
</scene>
|
</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-->
|
<!--Controllers-->
|
||||||
<scene sceneID="swa-DT-VKS">
|
<scene sceneID="swa-DT-VKS">
|
||||||
<objects>
|
<objects>
|
||||||
@ -257,7 +329,7 @@
|
|||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Controller Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="VBO-V1-Wfu">
|
<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"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
<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"/>
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
@ -272,9 +344,6 @@
|
|||||||
</label>
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
</tableViewCellContentView>
|
</tableViewCellContentView>
|
||||||
<connections>
|
|
||||||
<segue destination="HzL-SB-qry" kind="unwind" identifier="unwindControllersSegue" unwindAction="unwindFromControllersSettingsViewController:" id="WJe-ZI-clo"/>
|
|
||||||
</connections>
|
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
</prototypes>
|
</prototypes>
|
||||||
<connections>
|
<connections>
|
||||||
@ -284,9 +353,11 @@
|
|||||||
</tableView>
|
</tableView>
|
||||||
<navigationItem key="navigationItem" title="Controllers" id="QK7-oi-2jJ"/>
|
<navigationItem key="navigationItem" title="Controllers" id="QK7-oi-2jJ"/>
|
||||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||||
|
<connections>
|
||||||
|
<segue destination="0QR-U9-gtx" kind="presentation" identifier="controllerInputsSegue" id="E3Y-yV-zT5"/>
|
||||||
|
</connections>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="owG-Kh-rfn" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="owG-Kh-rfn" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
<exit id="HzL-SB-qry" userLabel="Exit" sceneMemberID="exit"/>
|
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="2221" y="471"/>
|
<point key="canvasLocation" x="2221" y="471"/>
|
||||||
</scene>
|
</scene>
|
||||||
@ -416,7 +487,7 @@
|
|||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="ssH-mM-uG6" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="ssH-mM-uG6" sceneMemberID="viewController">
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="Ckw-ES-lkE">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
<nil name="viewControllers"/>
|
<nil name="viewControllers"/>
|
||||||
@ -428,5 +499,76 @@
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="889" y="471"/>
|
<point key="canvasLocation" x="889" y="471"/>
|
||||||
</scene>
|
</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>
|
</scenes>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@ -46,8 +46,8 @@ class SettingsViewController: UITableViewController
|
|||||||
{
|
{
|
||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalControllerDidConnect(_:)), name: .externalControllerDidConnect, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidConnect(_:)), name: .externalGameControllerDidConnect, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalControllerDidDisconnect(_:)), name: .externalControllerDidDisconnect, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidDisconnect(_:)), name: .externalGameControllerDidDisconnect, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad()
|
override func viewDidLoad()
|
||||||
@ -64,6 +64,13 @@ class SettingsViewController: UITableViewController
|
|||||||
|
|
||||||
if let indexPath = self.tableView.indexPathForSelectedRow
|
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)
|
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
|
private extension SettingsViewController
|
||||||
{
|
{
|
||||||
@IBAction func beginChangingControllerOpacity(with sender: UISlider)
|
@IBAction func beginChangingControllerOpacity(with sender: UISlider)
|
||||||
@ -154,12 +149,12 @@ private extension SettingsViewController
|
|||||||
|
|
||||||
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)
|
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)
|
self.tableView.reloadSections(IndexSet(integer: Section.controllers.rawValue), with: .none)
|
||||||
}
|
}
|
||||||
@ -190,9 +185,9 @@ extension SettingsViewController
|
|||||||
{
|
{
|
||||||
cell.detailTextLabel?.text = UIDevice.current.name
|
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
|
cell.detailTextLabel?.text = controller.name
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
1
Podfile
1
Podfile
@ -9,6 +9,7 @@ target 'Delta' do
|
|||||||
pod 'SDWebImage', '~> 3.8'
|
pod 'SDWebImage', '~> 3.8'
|
||||||
pod 'Fabric', '~> 1.6.0'
|
pod 'Fabric', '~> 1.6.0'
|
||||||
pod 'Crashlytics', '~> 3.8.0'
|
pod 'Crashlytics', '~> 3.8.0'
|
||||||
|
pod 'SMCalloutView'
|
||||||
end
|
end
|
||||||
|
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ PODS:
|
|||||||
- SDWebImage (3.8.2):
|
- SDWebImage (3.8.2):
|
||||||
- SDWebImage/Core (= 3.8.2)
|
- SDWebImage/Core (= 3.8.2)
|
||||||
- SDWebImage/Core (3.8.2)
|
- SDWebImage/Core (3.8.2)
|
||||||
|
- SMCalloutView (2.1.5)
|
||||||
- SQLite.swift (0.11.3):
|
- SQLite.swift (0.11.3):
|
||||||
- SQLite.swift/standard (= 0.11.3)
|
- SQLite.swift/standard (= 0.11.3)
|
||||||
- SQLite.swift/standard (0.11.3)
|
- SQLite.swift/standard (0.11.3)
|
||||||
@ -15,6 +16,7 @@ DEPENDENCIES:
|
|||||||
- Fabric (~> 1.6.0)
|
- Fabric (~> 1.6.0)
|
||||||
- FileMD5Hash (~> 2.0.0)
|
- FileMD5Hash (~> 2.0.0)
|
||||||
- SDWebImage (~> 3.8)
|
- SDWebImage (~> 3.8)
|
||||||
|
- SMCalloutView
|
||||||
- SQLite.swift (~> 0.11.0)
|
- SQLite.swift (~> 0.11.0)
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
@ -22,8 +24,9 @@ SPEC CHECKSUMS:
|
|||||||
Fabric: 5911403591946b8228ab1c51d98f1d7137e863c6
|
Fabric: 5911403591946b8228ab1c51d98f1d7137e863c6
|
||||||
FileMD5Hash: 3ed69cc19a21ff4d30ae8833fc104275ad2c7de0
|
FileMD5Hash: 3ed69cc19a21ff4d30ae8833fc104275ad2c7de0
|
||||||
SDWebImage: '098e97e6176540799c27e804c96653ee0833d13c'
|
SDWebImage: '098e97e6176540799c27e804c96653ee0833d13c'
|
||||||
|
SMCalloutView: 5c0ee363dc8e7204b2fda17dfad38c93e9e23481
|
||||||
SQLite.swift: 99b36c22084427f0abbeb957556ce1528cf10bb3
|
SQLite.swift: 99b36c22084427f0abbeb957556ce1528cf10bb3
|
||||||
|
|
||||||
PODFILE CHECKSUM: de6e2bf57dcf8e9fe6b622b181fd87d3855641e6
|
PODFILE CHECKSUM: 598f830560ac5b18bbe0eb40134a1719f38f12f1
|
||||||
|
|
||||||
COCOAPODS: 1.2.1
|
COCOAPODS: 1.2.1
|
||||||
|
|||||||
5
Pods/Manifest.lock
generated
5
Pods/Manifest.lock
generated
@ -6,6 +6,7 @@ PODS:
|
|||||||
- SDWebImage (3.8.2):
|
- SDWebImage (3.8.2):
|
||||||
- SDWebImage/Core (= 3.8.2)
|
- SDWebImage/Core (= 3.8.2)
|
||||||
- SDWebImage/Core (3.8.2)
|
- SDWebImage/Core (3.8.2)
|
||||||
|
- SMCalloutView (2.1.5)
|
||||||
- SQLite.swift (0.11.3):
|
- SQLite.swift (0.11.3):
|
||||||
- SQLite.swift/standard (= 0.11.3)
|
- SQLite.swift/standard (= 0.11.3)
|
||||||
- SQLite.swift/standard (0.11.3)
|
- SQLite.swift/standard (0.11.3)
|
||||||
@ -15,6 +16,7 @@ DEPENDENCIES:
|
|||||||
- Fabric (~> 1.6.0)
|
- Fabric (~> 1.6.0)
|
||||||
- FileMD5Hash (~> 2.0.0)
|
- FileMD5Hash (~> 2.0.0)
|
||||||
- SDWebImage (~> 3.8)
|
- SDWebImage (~> 3.8)
|
||||||
|
- SMCalloutView
|
||||||
- SQLite.swift (~> 0.11.0)
|
- SQLite.swift (~> 0.11.0)
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
@ -22,8 +24,9 @@ SPEC CHECKSUMS:
|
|||||||
Fabric: 5911403591946b8228ab1c51d98f1d7137e863c6
|
Fabric: 5911403591946b8228ab1c51d98f1d7137e863c6
|
||||||
FileMD5Hash: 3ed69cc19a21ff4d30ae8833fc104275ad2c7de0
|
FileMD5Hash: 3ed69cc19a21ff4d30ae8833fc104275ad2c7de0
|
||||||
SDWebImage: '098e97e6176540799c27e804c96653ee0833d13c'
|
SDWebImage: '098e97e6176540799c27e804c96653ee0833d13c'
|
||||||
|
SMCalloutView: 5c0ee363dc8e7204b2fda17dfad38c93e9e23481
|
||||||
SQLite.swift: 99b36c22084427f0abbeb957556ce1528cf10bb3
|
SQLite.swift: 99b36c22084427f0abbeb957556ce1528cf10bb3
|
||||||
|
|
||||||
PODFILE CHECKSUM: de6e2bf57dcf8e9fe6b622b181fd87d3855641e6
|
PODFILE CHECKSUM: 598f830560ac5b18bbe0eb40134a1719f38f12f1
|
||||||
|
|
||||||
COCOAPODS: 1.2.1
|
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 @"iVBORw0KGgoAAAANSUhEUgAAACkAAABGCAYAAABRwr15AAAAHGlET1QAAAACAAAAAAAAACMAAAAoAAAAIwAAACMAAAKHOsNB2AAAAlNJREFUaAXsVVmKAlEMDANzCEWvIC644IILorjgAvrhh+iZxGPlCnOXMQ3VpMN7PXarMy3MR1FJpSovtB8SEX28AejzdmS2MRwOv5JiNBolyiT123voer3yb+JyuSR+j87nM1ucTicWQEcNhi7s88LjymDmY2TAdDweOeugw+HAWQft93ve7XYsnBZJ8tZre9cNtNlseLvdsrCFT9c+7fHVvjfu9dNqtWLBcrmMsNWlX6/XEQ8yLi80FyMn7KolA11qms/nrLFYLIJeGDXm6O1sNpuFO+CxGfSWkUUODJ/MaTKZ8HQ6DSA1oDWp43yYIWt7u8vu03OdhY/G4zELbv8KAft66M9k+6ZvN93+ghgYDAZhHadhpvmRrN6DWu+jXq/HQL/fZw3RpbcMD3KYwysPaA26MGY2Cz/mOkPdbpezDmq32+xCq9UK9U6nE9Yub1JN70Ot37P7qNlsMiBG1MK217NX1vZdajQarCGPo0etGcfBIwwNPjvTHj2D7tOwL3JkvV4PDgTrsEvTc9Q/+eLmdoaearUa34tqtXq3V+9Mm8MOkgUuVCoVFiSZwZ8mK+/oPN4VjbAwjsvlcrAgzuObPSNLpVKJfZAHMNM1tDjWfl3HZTDTfqmpWCyyRaFQYAF01JYx1/yKLGFpljk4Mp/PB18Oh6IHQ3ex9djelYEGr2XMwSQGbULvYwnamUuzHuldPpdms+GRuVyOATH5artAeyWje1unzZJerJfiSDAeQA9GBgxd86PZ4EgsjHsIHh+/Mhs50nfAX+v/Rz7rF3iLL/kNAAD//2Bk4sgAAAQXSURBVO2Va08TQRSGB9TWO2jxUgRapFeioGi8XypGImoA72hQf4IfVDCE+AtrwheDMfBb8H3WObhstvRCNSTa5M10Zs6c95mzuzMunU5Xd7rcTgeE7z9ku57S/0r+W5UcGBioxqm/v7/a29v7V44nfPCL42DMlUqlalTFYrE6NDQULOrr6/tjsMCRHxD88I2y0HeaXIvRai6XW5GWBwcHg0TtrqoBkh8f/MSxGsOy5vSbk954vVWL3iUSiS/a5fd8Pt920DAg+fHBD1/vD4MxwedeeL1UOyu9kl5Lc1q41G7QGoBL+Hlf/OGAx9jclDrTXjNqn0jPJAJmBbrYLtAagIv4eD988YfDmOBz97wm1N6XJqVHEpMseC7QzwJd2c6jjwFcIS/5vQ9++OIPBzzG5q6pg65LN6Xb0rhE0AOJxU+VcKFV0BqAC+T1+fHBD1/84YDH2NxZdUakUem8dEG6LBFUkVjMDh8LdL5Z0BqA8+TzeclfkfDDF3844IELPnfaK6e2IJWkMxKBlyR2dVditzPNgG4ByDtHPvKSHx/88MUfDniMzZ1UJy31Sn3SgMRkUWIX7Iyyj0u8Lw2B1gEkD/nIS3588MMXfzjggQs+1y0dkY5KKem4xCTB7GZYGpOuSnekuqANAJKHfOQlPz744Ys/HPDABZ/b73VA7UHpsEQAwackdleWeBwGytdXs6J6b6vcJP6g5iueJ15inQGSj7zkxwc/fPGHAx5jc3vUQQkpKe2TCOiSeiTKnpV4V85JVySMAtBkMvkp/DFxD4cBmVdsGJD15CFfViI/Pvjhiz8c8Bib61THtEv/d0sEsQt2Rel5DFkpDnRaIB8BLRQKy6reV1r6jGvNtMSG2FgUkLzkxwc/fPGHw5g63fr6+oY00eEnCWQn7OqQxGPgBc5KYdCK+gBMCehDJpNZKZfL32jpM+7nK2qjgOQjL/nxwQ9f4Do2cYU7/CfABzYDOqE1DwX2PpVK/aClLzFekVoGDJiikE2CjgqAA5izblwCatK39BlnnjieQFZquILGtvGobcBaJWukokXFjUgXJb78G9It39JnnHnislLTgEHRDCquVdJ6oJxteWlYolpjEmC09BlnnriWAOtCBgFbg56QObfDoFSQeKRl39JnnHniGvpIYosVNxgdk0GtinIjHJM4SoChYhnf0meceeLqfsVRX+vXfCctwFqZREH3aoyboUuiSj0StwZVo6XPOPPEEZ+QYo8Z84lrG4ZksX5hUG4DjDmE7ToFqFui5YBmnHniiG8aMPCNI99qTEYGarcT1eGmMGCgDIxxq57dIpsO6q28bK6pSm4s+g3K7WCwVAogk1XO4IhtGhDPliCDhb9AraoAGDBQYbAArlXAbUFaVSPAQG9SOK7V/z8B/0J0prY8CNcAAAAASUVORK5CYII="; }
|
||||||
|
+ (NSString *)SMCalloutViewBottomAnchor_2x { return @"iVBORw0KGgoAAAANSUhEUgAAAFIAAACMCAYAAADvGP7EAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAHGlET1QAAAACAAAAAAAAAEYAAAAoAAAARgAAAEYAAAGukzENIwAAAXpJREFUeAHs2smJgmEURNGKwyRUVEQFFZxAQcUJHKIwGNOqRa87FjuIeovm5y7uVvDwvsKF+n6/otwAxKJDAhLI/DlWTprm87kpN9DpdPqh3ECfz+eXcgO93+8fyg30er1MuYGez6cpNwCy6JD0eDxMuQGQRYek+/1uyg10u91MuYGu16spNwCy6JB0uVxMuYHO57MpN9DxeDTlBjocDqbcAMiiQ9J+vzflBtrtdqbcQNvt1pQbAFl0SNpsNqbcQOv12pQbaLVamXIDLZdLU26gxWJhyg34y0rRX3Y0m81MuQGQRYek6XRqyg00mUxMuYHG47EpNwCy6JA0Go1MuYGGw6EpN9BgMDDlBkAWHZL6/b4pN1Cv1zPlBkAWHZK63a4pN1Cn0zHlBmq326bcAMiiQ1Kr1TLlBkAWHRKQQObPsXLSuEgukots5K8EnjZPm6fN0678udC0z2Ij2Ug2ko1s2q5Vfh82ko1kI9nIyk1p2mexkWzk/9rIPwAAAP//UHDJMwAAB+NJREFU7ZmJdhRFGIULQUkiQiImkYhmWAVEFhERUBxZRDZRkE1W9U0EUV8OHgnv16fv2KeZ7pme6WUmds65pyq136/+qunpCSsrKy9bjc8gtBDHhwjDFmRJJ7IF2YIs50iWdbW1EdlGZBuRa/Jxqz3a7dFuj3Z7tMt6VFiL47R3ZHtHtndke0euxbutLE+h0+m8HFWrq6trJrrwMioH+oVxOtN3LcAcF2IEcmFh4WVRLS4uRpHoTZhmmEmIHHO8FeVB+zBKJ/dZXl7uHYdphJmEiBf7GiUN+vu9qGZmZp5rshdMOK0wMyC+mJ2d/acoj7h9+E2ZYdWDPs0wC0Aclgvtwq8jKJpgGmHmQPw7EVCjMAmPNECeHqveeqK8FU0mmH9OyzEfANHw7I/UvknzGFEX7g/QA9VbD5X3gAz+BE0DzAEQIx/ykgSGV/smHcQp3FGjPN1V/b1YvyhFDMrgBvtYMJ9NamTmQPxLHpLwDMw+7RsGeYyoCzdydFN16GfpViw6GS4T9oBOIswBEDldjjyCA3CGdlt5POPdHPJYhatqmKVrqvsh1o9Kf5IYzGCByuSO0EeTBDML4tzc3HOtGYiOQDzgxeDwiFc82z8ssjhRHi700XcqQxdjXVJ6WboiMeB1iYnYKSZnF3vROQkwh4TImlk7HvCCJ7zhEa94xrs5mEs/ZuGMGqb1jcpQN9ZZpeckBmBQBmcX2C0mJ0KT0fmwSZgDIHKUfYxZM2vHA17wZHB4xTPeu7HMJc2L/8MXGTqh8i+lk9Ip6bT0tdSVGJyJDJQj4Oj0UW8EZgGI3IGsmbUbIJ4MDq94xjsMYAGTLF7hU1X20yGVH5aOSEelY9JxiQGZgN1hUsKdI8COcrckj3oEc35+/oVU+dfJNETmZG6djudalyPRR5m1smbWjge84AlveMQrnvEOA1jApB8rysKeDO1VOdon7Zc+kejAwJ9LTPaV1JUcndeU55jUDnMIiMn7kDWyVk4Ua+9KeMET3vCIVzzjHQbmkcUrfKRGWVpVXUfaKe2WGIyBDZRdI/TZyfPS9xLHpFaYI0BkjayVNbN2PODFAPGIVzzjvSPBIosT5WE5Q++rHK1IH0gfSgzGwExyQCLUCX92kjuFy5kPI8PkIr8rEQ2VHPMxILLWMxJrxwNHF094wyNe8Yx3GMACZfEKC6rsp3dVvjXWYjzANqXbJSbZJRHyROdnEgsC5lnJMLnIM2EuLS2N9T4zCZGx+tyJbOBdiauGU8IGszbWyFpZM2vHA17whDc84hVoeDcHmPRjRVnYlKF3VL451rxSGjPgksQk7NYOiR08KB2V+FSrBWYJEFkra2btjkI84Q2PeMUz3s0BJlm8wowq05pVmTWn/NvxAAzI4O9JhDk715FYCBezYXJxVxaZJUJkzaydgMALnvCGR7wCDe8wMA/SNC/+D2/m6C3VoY0SAzAgg2+R2DFCnzukI/FpZpg8a1UCs2SIrLkj4QEveMIbHg0P7+aQxyq8oYZprVdZUhv0P4MYKLvk6OQYcBlzt1QKswSIbDCnhg1nrayZteOB+w9PeCNo8IpnvCdZkE/z4v+wboDciQEYlN0hlNkxw+RCNkweGQ5ILLi0yBwTIqcjCyJr91HGE97waID2n8/p1atXIU8a0AMwoGE6OpmYC5iFVAazBoh4MMRkFOI58p/HiLpciO7swZQ2AtO/n6cecZ5pPQ+lvEecYSJxbIhDg4waNhyZY0LkqkneiT7OpUAsBLJJmJMOsTDImmE+5c1N/G3Fb3GeFjzOlUciTEYCWSPMB3r9ZZi8CgPig8SdyFdPvoJelS5KZ6XknQhEniD8iFP6cTbEkUHWAPOOANyT7gvgH4h8XEbdREEcC2TFMG8K1m2JFw4AReQpo25iIhEOY4OsCOYVgbou3Yih3VKKAEgZdbRp/DgbYikgS4B5RFD8Dehb5S9IlyTuPqARfYg8ZdTRhraN3YlJiKWBLBHmacHpSuclIg5ol2ORp4y6rkRbNoCNqPWDJQ2xVJAlwDwsIMelkxLvNIm4cxLgEHnKqKMNbenTOMTSQY4Jc7+gHJKOSSekUxLQ+DkAkaeMOtrQlj67pVoecfCXpcyKrA7DlMvYulhvKF0vbZB4GbBRmpPSLzr48WiXtE86KHFcgcXRBRwiTxl1tKEtfRqHCJNKQEYDF4O5TUCAuVPaKxFpwCLqOL6IPGXU0Ya29KHvosQbKH93ZsPYODaQjWRDo80dJhBGaVMZyIIwkz9dEGFE2h7pY4nIQ+QpcxRuV56fBhqHGHkdhX6RPjI6zDGfV7ut0rLEC2IgAbQj7YhFnjLqaENb+tC3sUg0i0ojsjfJYJibBGOLxPEkOoFEtAEsKcqoow1t6UNf7t3aj7P9kdYCMpooH+asQPBbyWaJCAMS0QYwji4iTxl1tKFt1u8rld+JSYi1ghwC5ozAEFlEGEcVUEQc0BB5yqhzFNKn0Ug00Noisjfh65HJpyqfsPzgBBTgEKFAJeKAhshTRp0B0oe+jIFqj8SeL2fqTGU4+QGEeSD4WdNADRVoFmXIAJOPN41BhF3tEekNE4x+MB1Zhkq0pWV4bgvARiE2CjIHqMEYVDp1vdNoQzxeU2ljEZk0nIhORympQaXTZJuJgIiXiQCZhBot6r9j/xo0oKfbT8L/EwlyEsAUXUMLUseyKLR+7UsZpN/A/7eyfwFew4OhAH+vjwAAAABJRU5ErkJggg=="; }
|
||||||
|
+ (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 @"iVBORw0KGgoAAAANSUhEUgAAACIAAAByCAYAAAA2yQM1AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAHGlET1QAAAACAAAAAAAAADkAAAAoAAAAOQAAADkAAARyl43hJQAABD5JREFUaAXsmFlLa1cUx3eH27m9rbXaem1R6lCHOqDiPOGEA84DjqCoKIIPivokqC/6LijavBX6IfoRCmUZE25vB9v7Tez/f3DJIU002TvF2+LDj5UEcvYva629zzox19fX5lXglZBgIh5FItvBtLS0SCxaW1t/Bj+As+bm5vna2tpnkRdI1nszPDwc8jM0NBTq7++/xMISDUgHmpqaypMloNcxJycnV8rp6emVcnZ2dnV+fv5nIBD4C7w8PDy86ujouJWDzE5FRcUTvZBrNBsbG6FobG5uhra2tsI7Ozvh3d3d5xD59fj4+I/9/f3fNVOQCZSVlX1sjHnNWWRmZkaiMTc3J2R+fv5icXExuLa2dgm58N7e3i9HR0e/tbe3a3a+z8zMfJcyLkJmenpa4oGylFpdXQ1ub2+HDw4OXrBUyIo0NjbuQuJ1lbERiluEspRBli6Wl5eDKGcYQs8pQqqqqopuZG6FEimXmZqakkSZnZ29WFpa8mR6e3uloaGBnEPkDVsZKxGKMzMrKyvB9fX1EM4ZT+YmK2/6hOLuGzM5OSk2UIY9A5nLnp4eqa+vl7q6unVIPAEJy5jx8XGxBT1zsbCwEBwZGaGE1NTUfAeJt30y7BevZ+7rFzM2Nia28AewRIwUAT9iYW5lldGeubdETiL8ASwRS4v7EPkJEh/EkrkrK2Z0dFRc0P66ERFIPAXvg3fAW4D9cm+JDOvrwsTEhJDq6moPLJoCPgLvgX+UKFZWzODgoLig/YWty0ONGfkM8P6jJfJnJeY9yQwMDIgLUUQ+h8SnIKGs/BsizyCRBj4B/qzc7qBo5TEYgsQFbXRfab6CQLSsaNNGLY/hvcIFTHRCMCR5QCIbZADtFe4gf9NGF+Hx7II2uk8kBwtngsjy8Oj3yhO1NC4S/K42enl5uRAslge0PNzKHwKettw9sUW6urrEBe0vjIxCsNg3IAt8Afy7526Rzs5OcaGvr09IaWmpBxYvANonqXjNk1YPt5gNazjuuaCl9YkUYuGvAbdxZMPGFuEQ7IKKlJSUCMHiHBkpog3LU1Z3TmyRtrY2caG7u1tIcXGxBxYtBv6do8c9b4LcOVHnkzsfOWM9ivo/1/4qKioS4hP5Eq/TgZ6wd4vow5Jt1P4qLCwUgoW/BczIf1REn0tsozZ6REZykREeavGXhtO3C3yUIAUFBR5YnKVJXORm6NXhN+GojZufny/kwUS0yfPy8oRYi+jQaxv9Irm5ufYiOvTaRm1ySmRlZdmL6GRlG7XRc3JyBP+TPJyINjtF0tLS7EUqKyvFBRVhWVJTU+1FdMSzjdrk6enpDy/CWYTZcMqIzpouUcviJKKzpk3kAZadne1tW+eM6IiXSOTcwSxkZGR4MSkZiVeAizMDKkCJpIroBW0jxZKSEVsB/d6jiGZC42NGNBMaNSMpKSlCrAcjvaBt/N+J/A0AAP//+DyM3gAABEZJREFU7ZnLblNXGIVNXAdaYmIbG+M4JAZKgd6g3IQo94u4tiqUXmgLpe0LdMQb8AqoQ2YMmCC1YsCEB2DgR0q/z+JHm01ixwQIgxPpk30S+6x11r/29iWlmZmZ/kro9Xp9aTQaA0ql0hewA+agDXWYgnVQgQlYs7CwUEoprcSEzy2M5AkWiRSJ5Ankx0VHikTyBPLjoiNFInkC+XHRkSKRPIH8+L3qyPz8fL9er/ue9RnvR1fvPWsYqdVqT1fFSLfbHbx5np2dHSRCKg9Wxcjc3NzASKfTGRipVqt3V8VIFLXVag2MTE5Ofv/OjdgLjUQa09PTDzFx6LmRj7ndAm/3A1aYiJK6Ykjj13dqJDURI6Eb/2DiGByEz+HtJOLqiGI6Ds2ECZbsvwhfhNzILL/bBDUY/dk3CrfcWzvhB27HgYlH5XL5B4TOwlE4AJ/BdkiNrOd4LXwAi38IH2XAJNwn2u12v9lsxn7RZxz3Oamr5BKcga9hP3wKGulCC0xktBGvbBxYHY8p5h1Ofh2+BcdyGo7APtgNW2EGmjANH8HwREaZQPgJI/hvamrqXqVS+ZsTujp+gmvwDZyHk3AY9sIu6EEHNsIG0MgklGEi/V4k7vP70l9D+JO/yR/wO9yEG2AvvoPLcA6Og3vIl/AJ+CXNZmhAFT6EkUZCLL9VXG5DmIg07IZjuQCnIfphUWPpumLi2yKN+G3R0ERSwds8OEUDt8AkfoGfwW6kaZzgOB3LVo7tR17UMPLK11aOxx+FchSW38AUHIe9CBNXuG83ToFpHID0fYhjSfsxtKhhRCHxilMUDwN2wnGYRJhwJO4ddmMPuFq2QRfysbzoB39bMhGvNPiR+6KwmICrQwN2wnKahCaOgSP5CqIbUdI8jRjLBI9d0ohCwVXui8Ih7hLVgMV0hTgOk9DEPvC1xZXSg7QbbuuxWpbcUdPla9SBgoE7puImoAFTsJh2wnGYhCZ2ggV1S29DA2LvsBsj04iOKJLi64acAcVPgmPQgClYTDvhOEwiNeFI3EnXQ57GkmMJI8d5UqCgGL3CR0BxE9gPe8HVYTHdL3oQSWiiBjGSZacRRhQJDnJfvGqF7YDi7pgm4Pa9A7aBxbQTjmOkCR6zaEnTjniFgTMXRX0V9coVdwTbwTFowCW6GVpgJxxHnkQUdOhIUiNeoTGnKOpVK9wDxR2BCWhgE0QKFjM6EePQRBkGJrgdmkaMRpEtCQqKV61wB0LcBDRQBw2Ygq+s6+C1TYQRZ5zi1YqiTVDY+BW3jKkBV4YG3DkrYApjJZGORoEUxQJnr3AVvHpHYAK5gXQUyx5HmIhEFMhRMERDOMTTBBY1wHNHdiI1EUacb45igbFH9MYf4jECE3itFFIznGMwWwVSFAti7nEbwi/EeezYCaQmIpH0xMPur1EwJT/ZSo4578snT49XcuJxn/vSfxrHffKbfHxhJE/zfxa16jD5HvXsAAAAAElFTkSuQmCC"; }
|
||||||
|
+ (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
|
## SQLite.swift
|
||||||
|
|
||||||
(The MIT License)
|
(The MIT License)
|
||||||
|
|||||||
@ -274,6 +274,191 @@ THE SOFTWARE.
|
|||||||
<key>Type</key>
|
<key>Type</key>
|
||||||
<string>PSGroupSpecifier</string>
|
<string>PSGroupSpecifier</string>
|
||||||
</dict>
|
</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>
|
<dict>
|
||||||
<key>FooterText</key>
|
<key>FooterText</key>
|
||||||
<string>(The MIT License)
|
<string>(The MIT License)
|
||||||
|
|||||||
@ -91,11 +91,13 @@ strip_invalid_archs() {
|
|||||||
if [[ "$CONFIGURATION" == "Debug" ]]; then
|
if [[ "$CONFIGURATION" == "Debug" ]]; then
|
||||||
install_framework "$BUILT_PRODUCTS_DIR/FileMD5Hash/FileMD5Hash.framework"
|
install_framework "$BUILT_PRODUCTS_DIR/FileMD5Hash/FileMD5Hash.framework"
|
||||||
install_framework "$BUILT_PRODUCTS_DIR/SDWebImage/SDWebImage.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"
|
install_framework "$BUILT_PRODUCTS_DIR/SQLite.swift/SQLite.framework"
|
||||||
fi
|
fi
|
||||||
if [[ "$CONFIGURATION" == "Release" ]]; then
|
if [[ "$CONFIGURATION" == "Release" ]]; then
|
||||||
install_framework "$BUILT_PRODUCTS_DIR/FileMD5Hash/FileMD5Hash.framework"
|
install_framework "$BUILT_PRODUCTS_DIR/FileMD5Hash/FileMD5Hash.framework"
|
||||||
install_framework "$BUILT_PRODUCTS_DIR/SDWebImage/SDWebImage.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"
|
install_framework "$BUILT_PRODUCTS_DIR/SQLite.swift/SQLite.framework"
|
||||||
fi
|
fi
|
||||||
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
|
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
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
|
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Crashlytics" "${PODS_ROOT}/Headers/Public/Fabric"
|
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'
|
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_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 "SQLite" -framework "Security" -framework "SystemConfiguration" -framework "UIKit"
|
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"
|
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||||
PODS_BUILD_DIR = $BUILD_DIR
|
PODS_BUILD_DIR = $BUILD_DIR
|
||||||
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
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
|
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Crashlytics" "${PODS_ROOT}/Headers/Public/Fabric"
|
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'
|
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_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 "SQLite" -framework "Security" -framework "SystemConfiguration" -framework "UIKit"
|
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"
|
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||||
PODS_BUILD_DIR = $BUILD_DIR
|
PODS_BUILD_DIR = $BUILD_DIR
|
||||||
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
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