Adds support for 3D Touch app icon game shortcuts

This commit is contained in:
Riley Testut 2018-01-04 14:17:59 -06:00
parent 2e36b8125e
commit a9c3e85df8
23 changed files with 1111 additions and 88 deletions

@ -1 +1 @@
Subproject commit 6658b7a7557278c9b846466a0599462c1aeb870b
Subproject commit 4947d10713ba31017e40df275f40a315cf0da897

View File

@ -49,6 +49,8 @@
BF4828841F9027B600028B97 /* Delta.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BF4828811F9027B600028B97 /* Delta.xcdatamodeld */; };
BF4828861F9028F500028B97 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4828851F9028F500028B97 /* System.swift */; };
BF4828881F90290F00028B97 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4828871F90290F00028B97 /* Action.swift */; };
BF525EE81FF5F370004AA849 /* DeepLinkController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF525EE71FF5F370004AA849 /* DeepLinkController.swift */; };
BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF525EE91FF6CD12004AA849 /* DeepLink.swift */; };
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */; };
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */; };
BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */; };
@ -85,6 +87,8 @@
BF6EE5EB1F7C5F8F0051AD6C /* GameControllerInputMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */; };
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, ); }; };
BF71CF871FE90006001F1613 /* AppIconShortcutsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF71CF861FE90006001F1613 /* AppIconShortcutsViewController.swift */; };
BF71CF8A1FE904B1001F1613 /* GameTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF71CF891FE904B1001F1613 /* GameTableViewCell.xib */; };
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.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 */; };
@ -115,7 +119,7 @@
BFF0742D1E9DC17500ACDF4A /* GBCDeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BFF6452E1F7CC5060056533E /* GameControllerInputMappingTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6B82A41F7CC2A300042BFB /* GameControllerInputMappingTransformer.swift */; };
BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */; };
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */; };
BFFA4C091E8A24D600D87934 /* GameTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA4C081E8A24D600D87934 /* GameTableViewCell.swift */; };
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */; };
BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFFA71E01AAC406100EE9DD1 /* Main.storyboard */; };
BFFC461E1D59823500AF2CC6 /* GamesPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFC461B1D59823500AF2CC6 /* GamesPresentationController.swift */; };
@ -180,6 +184,8 @@
BF4828831F9027B600028B97 /* Delta.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Delta.xcdatamodel; sourceTree = "<group>"; };
BF4828851F9028F500028B97 /* System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = "<group>"; };
BF4828871F90290F00028B97 /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = "<group>"; };
BF525EE71FF5F370004AA849 /* DeepLinkController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkController.swift; sourceTree = "<group>"; };
BF525EE91FF6CD12004AA849 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadControllerSkinImageOperation.swift; sourceTree = "<group>"; };
BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadImageURLOperation.swift; sourceTree = "<group>"; };
BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridCollectionViewCell.swift; sourceTree = "<group>"; };
@ -221,6 +227,8 @@
BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _GameControllerInputMapping.swift; sourceTree = "<group>"; };
BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameControllerInputMapping.swift; sourceTree = "<group>"; };
BF70798B1B6B464B0019077C /* ZipZap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZipZap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BF71CF861FE90006001F1613 /* AppIconShortcutsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconShortcutsViewController.swift; sourceTree = "<group>"; };
BF71CF891FE904B1001F1613 /* GameTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GameTableViewCell.xib; sourceTree = "<group>"; };
BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = "<group>"; };
BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridMenuViewController.swift; sourceTree = "<group>"; };
BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Delta.swift"; sourceTree = "<group>"; };
@ -243,7 +251,7 @@
BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveStateMigrationPolicy.swift; sourceTree = "<group>"; };
BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBCDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputStreamOutputWriter.swift; sourceTree = "<group>"; };
BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameMetadataTableViewCell.swift; sourceTree = "<group>"; };
BFFA4C081E8A24D600D87934 /* GameTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameTableViewCell.swift; sourceTree = "<group>"; };
BFFA71D71AAC406100EE9DD1 /* Delta.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Delta.app; sourceTree = BUILT_PRODUCTS_DIR; };
BFFA71DB1AAC406100EE9DD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -343,12 +351,22 @@
path = "Game Selection";
sourceTree = "<group>";
};
BF525EE61FF5F355004AA849 /* Deep Linking */ = {
isa = PBXGroup;
children = (
BF525EE91FF6CD12004AA849 /* DeepLink.swift */,
BF525EE71FF5F370004AA849 /* DeepLinkController.swift */,
);
path = "Deep Linking";
sourceTree = "<group>";
};
BF5942571E09BB5D0051894B /* Components */ = {
isa = PBXGroup;
children = (
BF4828871F90290F00028B97 /* Action.swift */,
BFE0229C1F5B56840052D888 /* Popover Menu */,
BF5942671E09BBB70051894B /* Collection View */,
BF71CF881FE90471001F1613 /* Table View */,
BF5942601E09BBA80051894B /* Loading */,
);
path = Components;
@ -467,6 +485,23 @@
path = "Import Options";
sourceTree = "<group>";
};
BF71CF851FE8FFF1001F1613 /* App Icon Shortcuts */ = {
isa = PBXGroup;
children = (
BF71CF861FE90006001F1613 /* AppIconShortcutsViewController.swift */,
);
path = "App Icon Shortcuts";
sourceTree = "<group>";
};
BF71CF881FE90471001F1613 /* Table View */ = {
isa = PBXGroup;
children = (
BFFA4C081E8A24D600D87934 /* GameTableViewCell.swift */,
BF71CF891FE904B1001F1613 /* GameTableViewCell.xib */,
);
path = "Table View";
sourceTree = "<group>";
};
BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */ = {
isa = PBXGroup;
children = (
@ -506,7 +541,6 @@
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */,
BF95E2761E4977BF0030E7AD /* GameMetadata.swift */,
BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */,
BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */,
);
path = OpenVGDB;
sourceTree = "<group>";
@ -534,6 +568,7 @@
BFAA1FEC1B8AA4FA00495943 /* Settings.swift */,
BF5E7F451B9A652600AE44F8 /* Settings.storyboard */,
BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */,
BF71CF851FE8FFF1001F1613 /* App Icon Shortcuts */,
BF11734E1DA32CEC00047DF8 /* Controllers */,
BF1DAD5B1D9F574900E752A7 /* Controller Skins */,
);
@ -620,6 +655,7 @@
BF59426C1E09BC450051894B /* Database */,
BF59428C1E09BCE50051894B /* Importing */,
BF930FFB1EB6D6EC00E8DBA0 /* Systems */,
BF525EE61FF5F355004AA849 /* Deep Linking */,
BF5942571E09BB5D0051894B /* Components */,
BF696B7E1D9B2AE6009639E0 /* Theming */,
BF090CEE1B490C1A00DCAB45 /* Extensions */,
@ -759,6 +795,7 @@
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */,
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */,
BF02D5DA1DDEBB3000A5E131 /* openvgdb.sqlite in Resources */,
BF71CF8A1FE904B1001F1613 /* GameTableViewCell.xib in Resources */,
BFFC46461D59861000AF2CC6 /* LaunchScreen.storyboard in Resources */,
BF353FF61C5D837600C1184C /* PauseMenu.storyboard in Resources */,
BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */,
@ -855,6 +892,7 @@
BF59427C1E09BC830051894B /* Cheat.swift in Sources */,
BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */,
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */,
BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */,
BF59427E1E09BC830051894B /* Game.swift in Sources */,
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */,
@ -875,15 +913,17 @@
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */,
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
BF525EE81FF5F370004AA849 /* DeepLinkController.swift in Sources */,
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */,
BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */,
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */,
BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */,
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
BF71CF871FE90006001F1613 /* AppIconShortcutsViewController.swift in Sources */,
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */,
BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */,
BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */,
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */,
BFFA4C091E8A24D600D87934 /* GameTableViewCell.swift in Sources */,
BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */,
BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */,
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */,

View File

@ -17,6 +17,8 @@ import Crashlytics
class AppDelegate: UIResponder, UIApplicationDelegate
{
var window: UIWindow?
private let deepLinkController = DeepLinkController()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
@ -35,12 +37,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate
}
// Database
DatabaseManager.shared.loadPersistentStores { (description, error) in
}
// Controllers
ExternalGameControllerManager.shared.startMonitoring()
// Deep Links
if let shortcut = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem
{
self.deepLinkController.handle(.shortcut(shortcut))
// false = we handled the deep link, so no need to call delegate method separately.
return false
}
return true
}
@ -78,8 +88,6 @@ extension AppDelegate
func configureAppearance()
{
self.window?.tintColor = UIColor.deltaPurple
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes[NSAttributedStringKey.foregroundColor.rawValue] = UIColor.white
}
}
@ -92,15 +100,20 @@ extension AppDelegate
@discardableResult private func openURL(_ url: URL) -> Bool
{
guard url.isFileURL else { return false }
if GameType(fileExtension: url.pathExtension) != nil || url.pathExtension.lowercased() == "zip"
if url.isFileURL
{
return self.importGame(at: url)
if GameType(fileExtension: url.pathExtension) != nil || url.pathExtension.lowercased() == "zip"
{
return self.importGame(at: url)
}
else if url.pathExtension.lowercased() == "deltaskin"
{
return self.importControllerSkin(at: url)
}
}
else if url.pathExtension.lowercased() == "deltaskin"
else
{
return self.importControllerSkin(at: url)
return self.deepLinkController.handle(.url(url))
}
return false
@ -145,3 +158,12 @@ extension AppDelegate
}
}
extension AppDelegate
{
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void)
{
let result = self.deepLinkController.handle(.shortcut(shortcutItem))
completionHandler(result)
}
}

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13528" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="6bq-zy-UZU">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="6bq-zy-UZU">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13526"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -18,52 +17,6 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="4cJ-4B-Kgt" customClass="GameMetadataTableViewCell" customModule="Delta" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="375" height="97"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4cJ-4B-Kgt" id="7ze-s0-mpI">
<rect key="frame" x="0.0" y="0.0" width="375" height="96.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DSH-Hk-snb" userLabel="Selected Background View">
<rect key="frame" x="0.0" y="0.0" width="375" height="97.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="BoxArt" translatesAutoresizingMaskIntoConstraints="NO" id="tNY-2F-llo">
<rect key="frame" x="15" y="8" width="80" height="80"/>
<constraints>
<constraint firstAttribute="width" secondItem="tNY-2F-llo" secondAttribute="height" multiplier="1:1" id="f4E-bV-L96"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Super Mario World" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DND-Fv-FyB">
<rect key="frame" x="110" y="38.5" width="250" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="DND-Fv-FyB" firstAttribute="leading" secondItem="tNY-2F-llo" secondAttribute="trailing" constant="15" id="71e-t3-7Av"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="DND-Fv-FyB" secondAttribute="bottom" constant="1" id="9No-RE-0xx"/>
<constraint firstItem="DND-Fv-FyB" firstAttribute="top" relation="greaterThanOrEqual" secondItem="7ze-s0-mpI" secondAttribute="top" constant="1" id="F9q-6H-sqC"/>
<constraint firstAttribute="trailing" secondItem="DND-Fv-FyB" secondAttribute="trailing" constant="15" id="KFv-7n-LrD"/>
<constraint firstItem="DND-Fv-FyB" firstAttribute="centerY" secondItem="7ze-s0-mpI" secondAttribute="centerY" id="YBX-t4-jkR"/>
<constraint firstItem="tNY-2F-llo" firstAttribute="top" secondItem="7ze-s0-mpI" secondAttribute="top" constant="8" id="bYX-gA-QvB"/>
<constraint firstAttribute="bottom" secondItem="tNY-2F-llo" secondAttribute="bottom" constant="8" id="fxr-wr-I6X"/>
<constraint firstItem="tNY-2F-llo" firstAttribute="leading" secondItem="7ze-s0-mpI" secondAttribute="leading" constant="15" id="hX2-Gr-Bnz"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="artworkImageView" destination="tNY-2F-llo" id="GqY-jv-rso"/>
<outlet property="artworkImageViewLeadingConstraint" destination="hX2-Gr-Bnz" id="be8-dr-c8K"/>
<outlet property="artworkImageViewTrailingConstraint" destination="71e-t3-7Av" id="y62-KO-y1r"/>
<outlet property="nameLabel" destination="DND-Fv-FyB" id="LhN-cA-8Hy"/>
<outlet property="selectedBackgroundView" destination="DSH-Hk-snb" id="hLY-4k-VxU"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="SB6-jW-dhZ" id="2aq-ZA-84E"/>
<outlet property="delegate" destination="SB6-jW-dhZ" id="WgY-cp-m7K"/>
@ -101,7 +54,4 @@
<point key="canvasLocation" x="1854" y="1002"/>
</scene>
</scenes>
<resources>
<image name="BoxArt" width="100" height="100"/>
</resources>
</document>

View File

@ -100,7 +100,7 @@
<inset key="sectionInset" minX="0.0" minY="20" maxX="0.0" maxY="20"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" misplaced="YES" 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">
<rect key="frame" x="0.0" y="20" width="60" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">

View File

@ -19,7 +19,7 @@
<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"/>
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Delta 0.6.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Str-BY-agW">
<rect key="frame" x="0.0" y="573" width="375" height="44"/>
<rect key="frame" x="0.0" y="673" width="375" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
@ -222,6 +222,30 @@
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="3D Touch" id="fdp-8c-oOc">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="c5i-qG-ir9" style="IBUITableViewCellStyleDefault" id="SSL-t4-QZj">
<rect key="frame" x="0.0" y="611" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="SSL-t4-QZj" id="hQB-Iy-bzy">
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="App Icon Shortcuts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="c5i-qG-ir9">
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="yXS-6u-1AN" kind="show" id="b79-OK-isV"/>
</connections>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
<connections>
<outlet property="dataSource" destination="eHi-aO-uGS" id="rsh-0y-8Ex"/>
@ -441,6 +465,51 @@
</objects>
<point key="canvasLocation" x="2220" y="1181.5592203898052"/>
</scene>
<!--App Icon Shortcuts-->
<scene sceneID="e9N-fv-yuQ">
<objects>
<tableViewController id="yXS-6u-1AN" customClass="AppIconShortcutsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="97" sectionHeaderHeight="18" sectionFooterHeight="18" id="BGy-Nh-8Yd">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="none" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="SwitchCell" rowHeight="44" id="Syl-dC-eKZ" customClass="SwitchTableViewCell">
<rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Syl-dC-eKZ" id="lw6-ca-UzI">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4bh-Lf-zgk">
<rect key="frame" x="310" y="6" width="51" height="31"/>
<connections>
<action selector="switchGameShortcutsModeWith:" destination="yXS-6u-1AN" eventType="valueChanged" id="bX8-gd-h4g"/>
</connections>
</switch>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="4bh-Lf-zgk" firstAttribute="trailing" secondItem="lw6-ca-UzI" secondAttribute="trailingMargin" id="N1l-8j-xNY"/>
<constraint firstItem="4bh-Lf-zgk" firstAttribute="centerY" secondItem="lw6-ca-UzI" secondAttribute="centerY" id="bTA-Ci-3mD"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="switchView" destination="4bh-Lf-zgk" id="lL9-4t-5VN"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="yXS-6u-1AN" id="gUb-Iy-f9K"/>
<outlet property="delegate" destination="yXS-6u-1AN" id="UM5-nl-szw"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="App Icon Shortcuts" id="wfE-7e-l8g"/>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Lzk-4m-LKw" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2220" y="1897"/>
</scene>
<!--Controller Skins-->
<scene sceneID="IN0-an-SWm">
<objects>

View File

@ -1,5 +1,5 @@
//
// GameMetadataTableViewCell.swift
// GameTableViewCell.swift
// Delta
//
// Created by Riley Testut on 3/27/17.
@ -8,7 +8,7 @@
import UIKit
class GameMetadataTableViewCell: UITableViewCell
class GameTableViewCell: UITableViewCell
{
@IBOutlet private(set) var nameLabel: UILabel!
@IBOutlet private(set) var artworkImageView: UIImageView!

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="FL0-zT-qa3" customClass="GameTableViewCell" customModule="Delta" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="97"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="FL0-zT-qa3" id="zSi-4a-DaH">
<rect key="frame" x="0.0" y="0.0" width="375" height="96.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5dT-zd-huQ" userLabel="Selected Background View">
<rect key="frame" x="0.0" y="0.0" width="375" height="96.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="BoxArt" translatesAutoresizingMaskIntoConstraints="NO" id="68X-vf-MNx">
<rect key="frame" x="15" y="8" width="80" height="80"/>
<constraints>
<constraint firstAttribute="width" secondItem="68X-vf-MNx" secondAttribute="height" multiplier="1:1" id="P6f-Lc-8B3"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Super Mario World" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hKz-BX-p8h">
<rect key="frame" x="110" y="38.5" width="250" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="hKz-BX-p8h" secondAttribute="bottom" constant="1" id="1JB-It-w1s"/>
<constraint firstItem="68X-vf-MNx" firstAttribute="leading" secondItem="zSi-4a-DaH" secondAttribute="leading" constant="15" id="CD9-Qz-0UL"/>
<constraint firstAttribute="trailing" secondItem="hKz-BX-p8h" secondAttribute="trailing" constant="15" id="RxT-jB-cvp"/>
<constraint firstItem="68X-vf-MNx" firstAttribute="top" secondItem="zSi-4a-DaH" secondAttribute="top" constant="8" id="T5j-O5-aTX"/>
<constraint firstItem="hKz-BX-p8h" firstAttribute="leading" secondItem="68X-vf-MNx" secondAttribute="trailing" constant="15" id="jks-s2-5ZX"/>
<constraint firstItem="hKz-BX-p8h" firstAttribute="top" relation="greaterThanOrEqual" secondItem="zSi-4a-DaH" secondAttribute="top" constant="1" id="swc-Ib-2wh"/>
<constraint firstItem="hKz-BX-p8h" firstAttribute="centerY" secondItem="zSi-4a-DaH" secondAttribute="centerY" id="uyb-vA-Qtb"/>
<constraint firstAttribute="bottom" secondItem="68X-vf-MNx" secondAttribute="bottom" constant="8" id="wGX-lV-ACr"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="artworkImageView" destination="68X-vf-MNx" id="Sda-Tl-WEd"/>
<outlet property="artworkImageViewLeadingConstraint" destination="CD9-Qz-0UL" id="jhw-i7-9ak"/>
<outlet property="artworkImageViewTrailingConstraint" destination="jks-s2-5ZX" id="vrM-OV-rsa"/>
<outlet property="nameLabel" destination="hKz-BX-p8h" id="gdI-v9-dj3"/>
<outlet property="selectedBackgroundView" destination="5dT-zd-huQ" id="jOr-DK-8bp"/>
</connections>
</tableViewCell>
</objects>
<resources>
<image name="BoxArt" width="100" height="100"/>
</resources>
</document>

View File

@ -62,6 +62,8 @@ final class DatabaseManager: NSPersistentContainer
private var gamesDatabase: GamesDatabase? = nil
private var validationManagedObjectContext: NSManagedObjectContext?
private init()
{
guard
@ -94,11 +96,39 @@ extension DatabaseManager
}
}
//MARK: - Update -
private extension DatabaseManager
{
func updateRecentGameShortcuts()
{
guard let managedObjectContext = self.validationManagedObjectContext else { return }
guard Settings.gameShortcutsMode == .recent else { return }
let fetchRequest = Game.recentlyPlayedFetchRequest
fetchRequest.returnsObjectsAsFaults = false
do
{
let games = try managedObjectContext.fetch(fetchRequest)
Settings.gameShortcuts = games
}
catch
{
print(error)
}
}
}
//MARK: - Preparation -
private extension DatabaseManager
{
func prepareDatabase(completion: @escaping () -> Void)
{
self.validationManagedObjectContext = self.newBackgroundContext()
NotificationCenter.default.addObserver(self, selector: #selector(DatabaseManager.validateManagedObjectContextSave(with:)), name: .NSManagedObjectContextDidSave, object: nil)
self.performBackgroundTask { (context) in
for system in System.supportedSystems
@ -137,7 +167,6 @@ private extension DatabaseManager
}
completion()
}
}
}
@ -502,6 +531,28 @@ extension DatabaseManager
}
}
//MARK: - Notifications -
private extension DatabaseManager
{
@objc func validateManagedObjectContextSave(with notification: Notification)
{
guard (notification.object as? NSManagedObjectContext) != self.validationManagedObjectContext else { return }
let insertedObjects = (notification.userInfo?[NSInsertedObjectsKey] as? Set<NSManagedObject>) ?? []
let updatedObjects = (notification.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? []
let deletedObjects = (notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject>) ?? []
let allObjects = insertedObjects.union(updatedObjects).union(deletedObjects)
if allObjects.contains(where: { $0 is Game })
{
self.validationManagedObjectContext?.perform {
self.updateRecentGameShortcuts()
}
}
}
}
//MARK: - Private -
private extension DatabaseManager
{

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13532" systemVersion="16G29" minimumToolsVersion="Xcode 7.0" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="1.0">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13772" systemVersion="16G1114" minimumToolsVersion="Xcode 7.0" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="1.0">
<entity name="Cheat" representedClassName="Cheat" syncable="YES">
<attribute name="code" attributeType="String" syncable="YES"/>
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
@ -54,6 +54,7 @@
</attribute>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="playedDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="type" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameType"/>
@ -130,7 +131,7 @@
<elements>
<element name="Cheat" positionX="-198" positionY="-63" width="128" height="165"/>
<element name="ControllerSkin" positionX="-387" positionY="90" width="128" height="135"/>
<element name="Game" positionX="-378" positionY="-54" width="128" height="180"/>
<element name="Game" positionX="-378" positionY="-54" width="128" height="195"/>
<element name="GameCollection" positionX="-585" positionY="-27" width="128" height="90"/>
<element name="GameControllerInputMapping" positionX="-387" positionY="90" width="128" height="105"/>
<element name="SaveState" positionX="-198" positionY="113" width="128" height="165"/>

View File

@ -55,6 +55,18 @@ public class Game: _Game, GameProtocol
}
}
extension Game
{
class var recentlyPlayedFetchRequest: NSFetchRequest<Game> {
let fetchRequest: NSFetchRequest<Game> = Game.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "%K != nil", #keyPath(Game.playedDate))
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Game.playedDate, ascending: false), NSSortDescriptor(keyPath: \Game.name, ascending: true)]
fetchRequest.fetchLimit = 4
return fetchRequest
}
}
extension Game
{
override public func prepareForDeletion()

View File

@ -22,6 +22,8 @@ public class _Game: NSManagedObject
@NSManaged public var name: String
@NSManaged public var playedDate: Date?
@NSManaged public var type: GameType
// MARK: - Relationships

View File

@ -54,6 +54,8 @@ class GamesDatabaseBrowserViewController: UITableViewController
self.view.backgroundColor = UIColor.deltaDarkGray
self.tableView.register(GameTableViewCell.nib!, forCellReuseIdentifier: RSTCellContentGenericCellIdentifier)
self.tableView.dataSource = self.dataSource
self.tableView.prefetchDataSource = self.dataSource
@ -61,7 +63,7 @@ class GamesDatabaseBrowserViewController: UITableViewController
self.tableView.separatorColor = UIColor.gray
self.dataSource.searchController.delegate = self
self.dataSource.searchController.searchBar.barStyle = .blackTranslucent
self.dataSource.searchController.searchBar.barStyle = .black
if #available(iOS 11, *)
{
@ -95,7 +97,7 @@ private extension GamesDatabaseBrowserViewController
/* Cell Configuration */
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, metadata, indexPath) in
self.configure(cell: cell as! GameMetadataTableViewCell, with: metadata, for: indexPath)
self.configure(cell: cell as! GameTableViewCell, with: metadata, for: indexPath)
}
@ -113,7 +115,7 @@ private extension GamesDatabaseBrowserViewController
self.dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
guard let image = image else { return }
let cell = cell as! GameMetadataTableViewCell
let cell = cell as! GameTableViewCell
let artworkDisplaySize = AVMakeRect(aspectRatio: image.size, insideRect: cell.artworkImageView.bounds)
let offset = (cell.artworkImageView.bounds.width - artworkDisplaySize.width) / 2
@ -150,7 +152,7 @@ private extension GamesDatabaseBrowserViewController
private extension GamesDatabaseBrowserViewController
{
func configure(cell: GameMetadataTableViewCell, with metadata: GameMetadata, for indexPath: IndexPath)
func configure(cell: GameTableViewCell, with metadata: GameMetadata, for indexPath: IndexPath)
{
cell.backgroundColor = UIColor.deltaDarkGray

View File

@ -0,0 +1,103 @@
//
// DeepLink.swift
// Delta
//
// Created by Riley Testut on 12/29/17.
// Copyright © 2017 Riley Testut. All rights reserved.
//
import UIKit
extension URL
{
init(action: DeepLink.Action)
{
var components = URLComponents()
components.host = action.type.rawValue
switch action
{
case .launchGame(let identifier): components.path = identifier
}
let url = components.url!
self = url
}
}
extension UIApplicationShortcutItem
{
convenience init(localizedTitle: String, action: DeepLink.Action)
{
var userInfo: [AnyHashable: Any]?
switch action
{
case .launchGame(let identifier): userInfo = [DeepLink.Key.identifier.rawValue: identifier]
}
self.init(type: action.type.rawValue, localizedTitle: localizedTitle, localizedSubtitle: nil, icon: nil, userInfo: userInfo)
}
}
extension DeepLink
{
enum Action
{
case launchGame(identifier: String)
var type: ActionType {
switch self
{
case .launchGame: return .launchGame
}
}
}
enum ActionType: String
{
case launchGame = "game"
}
enum Key: String
{
case identifier
case game
}
}
enum DeepLink
{
case url(URL)
case shortcut(UIApplicationShortcutItem)
var actionType: ActionType? {
switch self
{
case .url(let url):
guard let host = url.host else { return nil }
let type = ActionType(rawValue: host)
return type
case .shortcut(let shortcut):
let type = ActionType(rawValue: shortcut.type)
return type
}
}
var action: Action? {
guard let type = self.actionType else { return nil }
switch (self, type)
{
case (.url(let url), .launchGame):
let identifier = url.lastPathComponent
return .launchGame(identifier: identifier)
case (.shortcut(let shortcut), .launchGame):
guard let identifier = shortcut.userInfo?[Key.identifier.rawValue] as? String else { return nil }
return .launchGame(identifier: identifier)
}
}
}

View File

@ -0,0 +1,83 @@
//
// DeepLinkController.swift
// Delta
//
// Created by Riley Testut on 12/28/17.
// Copyright © 2017 Riley Testut. All rights reserved.
//
import UIKit
extension Notification.Name
{
static let deepLinkControllerLaunchGame = Notification.Name("deepLinkControllerLaunchGame")
}
extension UIViewController
{
var allowsDeepLinkingDismissal: Bool {
return true
}
}
struct DeepLinkController
{
private var window: UIWindow? {
guard let delegate = UIApplication.shared.delegate, let window = delegate.window else { return nil }
return window
}
private var topViewController: UIViewController? {
guard let window = self.window else { return nil }
var topViewController = window.rootViewController
while topViewController?.presentedViewController != nil
{
guard !(topViewController?.presentedViewController is UIAlertController) else { break }
topViewController = topViewController?.presentedViewController
}
return topViewController
}
}
extension DeepLinkController
{
@discardableResult func handle(_ deepLink: DeepLink) -> Bool
{
guard let action = deepLink.action else { return false }
switch action
{
case .launchGame(let identifier): return self.launchGame(withIdentifier: identifier)
}
}
}
private extension DeepLinkController
{
func launchGame(withIdentifier identifier: String) -> Bool
{
guard let topViewController = self.topViewController, topViewController.allowsDeepLinkingDismissal else { return false }
let fetchRequest: NSFetchRequest<Game> = Game.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(Game.identifier), identifier)
fetchRequest.returnsObjectsAsFaults = false
do
{
guard let game = try DatabaseManager.shared.viewContext.fetch(fetchRequest).first else { return false }
NotificationCenter.default.post(name: .deepLinkControllerLaunchGame, object: self, userInfo: [DeepLink.Key.game: game])
}
catch
{
print(error)
return false
}
return true
}
}

View File

@ -93,6 +93,21 @@ class GameViewController: DeltaCore.GameViewController
}
}
private var _deepLinkResumingSaveState: SaveStateProtocol? {
didSet {
guard let saveState = oldValue, _deepLinkResumingSaveState == nil else { return }
do
{
try FileManager.default.removeItem(at: saveState.fileURL)
}
catch
{
print(error)
}
}
}
private var _isLoadingSaveState = false
private var context = CIContext(options: [kCIContextWorkingColorSpace: NSNull()])
@ -127,6 +142,7 @@ class GameViewController: DeltaCore.GameViewController
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalGameControllerDidDisconnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.didEnterBackground(with:)), name: .UIApplicationDidEnterBackground, object: UIApplication.shared)
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.settingsDidChange(with:)), name: .settingsDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.deepLinkControllerLaunchGame(with:)), name: .deepLinkControllerLaunchGame, object: nil)
}
deinit
@ -364,7 +380,11 @@ extension GameViewController
@IBAction private func unwindFromGamesViewController(with segue: UIStoryboardSegue)
{
self.pausedSaveState = nil
self.emulatorCore?.resume()
if let emulatorCore = self.emulatorCore, emulatorCore.state == .paused
{
emulatorCore.resume()
}
}
// MARK: - KVO
@ -375,10 +395,39 @@ extension GameViewController
guard let rawValue = change?[.oldKey] as? Int, let previousState = EmulatorCore.State(rawValue: rawValue) else { return }
if let saveState = _deepLinkResumingSaveState, let emulatorCore = self.emulatorCore, emulatorCore.state == .running
{
emulatorCore.pause()
do
{
try emulatorCore.load(saveState)
}
catch
{
print(error)
}
_deepLinkResumingSaveState = nil
emulatorCore.resume()
}
if previousState == .stopped
{
self.emulatorCore?.updateCheats()
}
if self.emulatorCore?.state == .running
{
DatabaseManager.shared.performBackgroundTask { (context) in
guard let game = self.game as? Game else { return }
let backgroundGame = context.object(with: game.objectID) as! Game
backgroundGame.playedDate = Date()
context.saveWithErrorLogging()
}
}
}
}
@ -812,7 +861,8 @@ extension GameViewController: GameViewControllerDelegate
func gameViewControllerShouldResumeEmulation(_ gameViewController: DeltaCore.GameViewController) -> Bool
{
return (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.isSelectingSustainedButtons && self.view.window != nil
let result = (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.isSelectingSustainedButtons && self.view.window != nil
return result
}
}
@ -858,4 +908,44 @@ private extension GameViewController
case .translucentControllerSkinOpacity: self.controllerView.translucentControllerSkinOpacity = Settings.translucentControllerSkinOpacity
}
}
@objc func deepLinkControllerLaunchGame(with notification: Notification)
{
guard let game = notification.userInfo?[DeepLink.Key.game] as? Game else { return }
self.game = game
if let pausedSaveState = self.pausedSaveState, game == (self.game as? Game)
{
// Launching current game via deep link, so we store a copy of the paused save state to resume when emulator core is started.
do
{
let temporaryURL = FileManager.default.uniqueTemporaryURL()
try FileManager.default.copyItem(at: pausedSaveState.fileURL, to: temporaryURL)
_deepLinkResumingSaveState = DeltaCore.SaveState(fileURL: temporaryURL, gameType: game.type)
}
catch
{
print(error)
}
}
if let pauseViewController = self.pauseViewController
{
let segue = UIStoryboardSegue(identifier: "unwindFromPauseMenu", source: pauseViewController, destination: self)
self.unwindFromPauseViewController(segue)
}
else if
let navigationController = self.presentedViewController as? UINavigationController,
let pageViewController = navigationController.topViewController?.childViewControllers.first as? UIPageViewController,
let gameCollectionViewController = pageViewController.viewControllers?.first as? GameCollectionViewController
{
let segue = UIStoryboardSegue(identifier: "unwindFromGames", source: gameCollectionViewController, destination: self)
self.unwindFromGamesViewController(with: segue)
}
self.dismiss(animated: true, completion: nil)
}
}

View File

@ -200,6 +200,8 @@ private extension GamesViewController
searchResultsController?.dataSource.predicate = searchValue.predicate
return nil
}
self.searchController?.searchBar.barStyle = .black
self.navigationItem.searchController = self.searchController
self.navigationItem.hidesSearchBarWhenScrolling = false

View File

@ -7,6 +7,7 @@
//
import UIKit
import Roxas
class LaunchViewController: UIViewController
{
@ -15,6 +16,8 @@ class LaunchViewController: UIViewController
private var presentedGameViewController: Bool = false
private var applicationLaunchDeepLinkGame: Game?
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.gameViewController?.preferredStatusBarStyle ?? .lightContent
}
@ -27,6 +30,13 @@ class LaunchViewController: UIViewController
return self.gameViewController
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
NotificationCenter.default.addObserver(self, selector: #selector(LaunchViewController.deepLinkControllerLaunchGame(with:)), name: .deepLinkControllerLaunchGame, object: nil)
}
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
@ -35,10 +45,33 @@ class LaunchViewController: UIViewController
{
self.presentedGameViewController = true
self.gameViewController.performSegue(withIdentifier: "showInitialGamesViewController", sender: nil)
self.transitionCoordinator?.animate(alongsideTransition: nil, completion: { (context) in
func showGameViewController()
{
self.view.bringSubview(toFront: self.gameViewContainerView)
})
self.setNeedsStatusBarAppearanceUpdate()
if #available(iOS 11.0, *)
{
self.setNeedsUpdateOfHomeIndicatorAutoHidden()
}
}
if let game = self.applicationLaunchDeepLinkGame
{
self.gameViewController.game = game
UIView.transition(with: self.view, duration: 0.3, options: [.transitionCrossDissolve], animations: {
showGameViewController()
}, completion: nil)
}
else
{
self.gameViewController.performSegue(withIdentifier: "showInitialGamesViewController", sender: nil)
self.transitionCoordinator?.animate(alongsideTransition: nil, completion: { (context) in
showGameViewController()
})
}
}
}
@ -49,3 +82,15 @@ class LaunchViewController: UIViewController
self.gameViewController = segue.destination as! GameViewController
}
}
private extension LaunchViewController
{
@objc func deepLinkControllerLaunchGame(with notification: Notification)
{
guard !self.presentedGameViewController else { return }
guard let game = notification.userInfo?[DeepLink.Key.game] as? Game else { return }
self.applicationLaunchDeepLinkGame = game
}
}

View File

@ -130,11 +130,11 @@ extension SaveStatesViewController
self.navigationController?.toolbar.barStyle = .blackTranslucent
self.updateTheme()
}
}
override func viewDidDisappear(_ animated: Bool)
override func viewWillDisappear(_ animated: Bool)
{
super.viewDidDisappear(animated)
super.viewWillDisappear(animated)
self.resetEmulatorCoreIfNeeded()
}

View File

@ -0,0 +1,355 @@
//
// AppIconShortcutsViewController.swift
// Delta
//
// Created by Riley Testut on 12/19/17.
// Copyright © 2017 Riley Testut. All rights reserved.
//
import UIKit
import DeltaCore
import Roxas
@objc(SwitchTableViewCell)
private class SwitchTableViewCell: UITableViewCell
{
@IBOutlet var switchView: UISwitch!
}
class AppIconShortcutsViewController: UITableViewController
{
private lazy var dataSource = RSTCompositeTableViewPrefetchingDataSource<Game, UIImage>(dataSources: [self.modeDataSource, self.shortcutsDataSource, self.gamesDataSource])
private let modeDataSource = RSTDynamicTableViewDataSource<Game>()
private let shortcutsDataSource = RSTArrayTableViewPrefetchingDataSource<Game, UIImage>(items: [])
private let gamesDataSource = RSTFetchedResultsTableViewPrefetchingDataSource<Game, UIImage>(fetchedResultsController: NSFetchedResultsController())
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
self.prepareDataSource()
}
override func viewDidLoad()
{
super.viewDidLoad()
self.tableView.register(GameTableViewCell.nib!, forCellReuseIdentifier: RSTCellContentGenericCellIdentifier)
if #available(iOS 11, *)
{
self.navigationItem.searchController = self.gamesDataSource.searchController
self.navigationItem.hidesSearchBarWhenScrolling = false
}
else
{
self.tableView.tableHeaderView = self.gamesDataSource.searchController.searchBar
}
self.tableView.dataSource = self.dataSource
self.tableView.allowsSelectionDuringEditing = true
self.updateShortcuts()
}
}
private extension AppIconShortcutsViewController
{
func prepareDataSource()
{
// Mode
self.modeDataSource.numberOfSectionsHandler = { 1 }
self.modeDataSource.numberOfItemsHandler = { _ in 1 }
self.modeDataSource.cellIdentifierHandler = { _ in "SwitchCell" }
// Shortcuts
self.shortcutsDataSource.items = Settings.gameShortcuts
// Games
let gamesFetchRequest: NSFetchRequest<Game> = Game.fetchRequest()
gamesFetchRequest.returnsObjectsAsFaults = false
gamesFetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Game.type, ascending: true), NSSortDescriptor(key: #keyPath(Game.name), ascending: true)]
let gamesFetchedResultsController = NSFetchedResultsController(fetchRequest: gamesFetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: #keyPath(Game.type), cacheName: nil)
self.gamesDataSource.fetchedResultsController = gamesFetchedResultsController
self.gamesDataSource.searchController.searchableKeyPaths = [#keyPath(Game.name)]
// Data Source
self.dataSource.proxy = self
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, game, indexPath) in
if indexPath.section == 0
{
self.configureModeCell(cell as! SwitchTableViewCell, for: indexPath)
}
else
{
self.configureGameCell(cell as! GameTableViewCell, with: game, for: indexPath)
}
}
self.dataSource.prefetchHandler = { (game, indexPath, completionHandler) in
guard indexPath.section > 0 else { return nil }
guard let artworkURL = game.artworkURL else { return nil }
let imageOperation = LoadImageURLOperation(url: artworkURL)
imageOperation.resultHandler = { (image, error) in
completionHandler(image, error)
}
return imageOperation
}
self.dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
guard indexPath.section > 0 else { return }
guard let image = image else { return }
let cell = cell as! GameTableViewCell
let artworkDisplaySize = AVMakeRect(aspectRatio: image.size, insideRect: cell.artworkImageView.bounds)
let offset = (cell.artworkImageView.bounds.width - artworkDisplaySize.width) / 2
// Offset artworkImageViewLeadingConstraint and artworkImageViewTrailingConstraint to right-align artworkImageView
cell.artworkImageViewLeadingConstraint.constant += offset
cell.artworkImageViewTrailingConstraint.constant -= offset
cell.artworkImageView.image = image
cell.artworkImageView.superview?.layoutIfNeeded()
}
self.dataSource.rowAnimation = .fade
}
func configureModeCell(_ cell: SwitchTableViewCell, for indexPath: IndexPath)
{
cell.textLabel?.text = NSLocalizedString("Recently Played Games", comment: "")
cell.textLabel?.backgroundColor = .clear
cell.switchView.isOn = (Settings.gameShortcutsMode == .recent)
cell.switchView.onTintColor = self.view.tintColor
}
func configureGameCell(_ cell: GameTableViewCell, with game: Game, for indexPath: IndexPath)
{
cell.nameLabel.textColor = .darkText
cell.backgroundColor = .white
cell.nameLabel.text = game.name
cell.artworkImageView.image = #imageLiteral(resourceName: "BoxArt")
cell.artworkImageViewLeadingConstraint.constant = 15
cell.artworkImageViewTrailingConstraint.constant = 15
cell.separatorInset.left = cell.nameLabel.frame.minX
cell.selectedBackgroundView = nil
switch (indexPath.section, Settings.gameShortcutsMode)
{
case (1, _):
cell.selectionStyle = .none
cell.contentView.alpha = 1.0
case (2..., .recent):
cell.selectionStyle = .none
cell.contentView.alpha = 0.3
case (2..., .manual):
cell.selectionStyle = .gray
cell.contentView.alpha = 1.0
default: break
}
}
}
private extension AppIconShortcutsViewController
{
func updateShortcuts()
{
switch Settings.gameShortcutsMode
{
case .recent:
let fetchRequest = Game.recentlyPlayedFetchRequest
fetchRequest.returnsObjectsAsFaults = false
do
{
let games = try DatabaseManager.shared.viewContext.fetch(fetchRequest)
self.shortcutsDataSource.setItems(games, with: [])
}
catch
{
print(error)
}
self.tableView.setEditing(false, animated: true)
case .manual: self.tableView.setEditing(true, animated: true)
}
Settings.gameShortcuts = self.shortcutsDataSource.items
}
func addShortcut(for game: Game)
{
guard self.shortcutsDataSource.items.count < 4 else { return }
guard !self.shortcutsDataSource.items.contains(game) else { return }
// No need to adjust destinationIndexPath, since it forwards change directly to table view.
let destinationIndexPath = IndexPath(row: self.shortcutsDataSource.items.count, section: 1)
let insertion = RSTCellContentChange(type: .insert, currentIndexPath: nil, destinationIndexPath: destinationIndexPath)
insertion.rowAnimation = .fade
var shortcuts = self.shortcutsDataSource.items
shortcuts.insert(game, at: destinationIndexPath.row)
self.shortcutsDataSource.setItems(shortcuts, with: [insertion])
self.updateShortcuts()
}
}
private extension AppIconShortcutsViewController
{
@IBAction func switchGameShortcutsMode(with sender: UISwitch)
{
if sender.isOn
{
Settings.gameShortcutsMode = .recent
}
else
{
Settings.gameShortcutsMode = .manual
}
self.tableView.beginUpdates()
self.updateShortcuts()
self.tableView.reloadSections(IndexSet(integersIn: 0 ..< self.tableView.numberOfSections), with: .fade)
self.tableView.endUpdates()
}
}
extension AppIconShortcutsViewController
{
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
guard indexPath.section == 0 else { return super.tableView(tableView, heightForRowAt: indexPath) }
return 44
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
switch section
{
case 0: return nil
case 1: return NSLocalizedString("Shortcuts", comment: "")
default:
let gameType = GameType(rawValue: self.gamesDataSource.fetchedResultsController.sections![section - 2].name)
let system = System(gameType: gameType)!
return system.localizedName
}
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String?
{
switch (section, Settings.gameShortcutsMode)
{
case (0, .recent): return NSLocalizedString("Your most recently played games will appear as shortcuts when 3D touching the app icon.", comment: "")
case (0, .manual): return NSLocalizedString("The games you've selected below will appear as shortcuts when 3D touching the app icon.", comment: "")
case (1, .recent): return " " // Return non-empty string since empty string changes vertical offset of section for some reason.
case (1, .manual): return NSLocalizedString("You may have up to 4 shortcuts.", comment: "")
default: return nil
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
guard indexPath.section > 1 else { return }
guard Settings.gameShortcutsMode == .manual else { return }
tableView.deselectRow(at: indexPath, animated: true)
let game = self.dataSource.item(at: indexPath)
self.addShortcut(for: game)
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
switch editingStyle
{
case .none: break
case .delete:
let deletion = RSTCellContentChange(type: .delete, currentIndexPath: indexPath, destinationIndexPath: nil)
deletion.rowAnimation = .fade
var shortcuts = self.shortcutsDataSource.items
shortcuts.remove(at: indexPath.row) // No need to adjust indexPath, since it forwards change directly to table view.
self.shortcutsDataSource.setItems(shortcuts, with: [deletion])
case .insert:
let game = self.dataSource.item(at: indexPath)
self.addShortcut(for: game)
}
self.updateShortcuts()
}
override func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String?
{
return NSLocalizedString("Remove", comment: "")
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle
{
switch indexPath.section
{
case 1: return .delete
case 2...: return .insert
default: return .none
}
}
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool
{
return false
}
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool
{
return (indexPath.section == 1)
}
override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath
{
let indexPath: IndexPath
switch proposedDestinationIndexPath.section
{
case 0: indexPath = IndexPath(row: 0, section: 1)
case 1: indexPath = proposedDestinationIndexPath
default: indexPath = IndexPath(row: self.shortcutsDataSource.items.count - 1, section: 1)
}
return indexPath
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
{
var items = self.shortcutsDataSource.items
let game = items.remove(at: sourceIndexPath.row)
items.insert(game, at: destinationIndexPath.row)
self.shortcutsDataSource.items = items
self.updateShortcuts()
}
}

View File

@ -35,6 +35,15 @@ extension Settings
}
}
extension Settings
{
enum GameShortcutsMode: String
{
case recent
case manual
}
}
struct Settings
{
/// Controllers
@ -66,9 +75,54 @@ struct Settings
}
}
static var gameShortcutsMode: GameShortcutsMode {
set { UserDefaults.standard.gameShortcutsMode = newValue.rawValue }
get {
let mode = GameShortcutsMode(rawValue: UserDefaults.standard.gameShortcutsMode) ?? .recent
return mode
}
}
static var gameShortcuts: [Game] {
set {
let identifiers = newValue.map { $0.identifier }
UserDefaults.standard.gameShortcutIdentifiers = identifiers
let shortcuts = newValue.map { UIApplicationShortcutItem(localizedTitle: $0.name, action: .launchGame(identifier: $0.identifier)) }
DispatchQueue.main.async {
UIApplication.shared.shortcutItems = shortcuts
}
}
get {
let identifiers = UserDefaults.standard.gameShortcutIdentifiers
do
{
let fetchRequest: NSFetchRequest<Game> = Game.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "%K IN %@", #keyPath(Game.identifier), identifiers)
fetchRequest.returnsObjectsAsFaults = false
let games = try DatabaseManager.shared.viewContext.fetch(fetchRequest).sorted(by: { (game1, game2) -> Bool in
let index1 = identifiers.index(of: game1.identifier)!
let index2 = identifiers.index(of: game2.identifier)!
return index1 < index2
})
return games
}
catch
{
print(error)
}
return []
}
}
static func registerDefaults()
{
let defaults = [#keyPath(UserDefaults.translucentControllerSkinOpacity): 0.7]
let defaults = [#keyPath(UserDefaults.translucentControllerSkinOpacity): 0.7, #keyPath(UserDefaults.gameShortcutsMode): GameShortcutsMode.recent.rawValue] as [String : Any]
UserDefaults.standard.register(defaults: defaults)
}
@ -163,4 +217,7 @@ private extension UserDefaults
{
@NSManaged var translucentControllerSkinOpacity: CGFloat
@NSManaged var previousGameCollectionIdentifier: String?
@NSManaged var gameShortcutsMode: String
@NSManaged var gameShortcutIdentifiers: [String]
}

View File

@ -19,6 +19,7 @@ private extension SettingsViewController
case controllers
case controllerSkins
case controllerOpacity
case threeDTouch
}
enum Segue: String
@ -130,6 +131,15 @@ private extension SettingsViewController
let percentage = String(format: "%.f", Settings.translucentControllerSkinOpacity * 100) + "%"
self.controllerOpacityLabel.text = percentage
}
func isSectionHidden(_ section: Section) -> Bool
{
switch section
{
case .threeDTouch: return self.view.traitCollection.forceTouchCapability != .available
default: return false
}
}
}
private extension SettingsViewController
@ -183,7 +193,15 @@ extension SettingsViewController
{
case .controllers: return 1 // Temporarily hide other controller indexes until controller logic is finalized
case .controllerSkins: return System.supportedSystems.count
default: return super.tableView(tableView, numberOfRowsInSection: sectionIndex)
default:
if isSectionHidden(section)
{
return 0
}
else
{
return super.tableView(tableView, numberOfRowsInSection: sectionIndex)
}
}
}
@ -226,6 +244,64 @@ extension SettingsViewController
case Section.controllers: self.performSegue(withIdentifier: Segue.controllers.rawValue, sender: cell)
case Section.controllerSkins: self.performSegue(withIdentifier: Segue.controllerSkins.rawValue, sender: cell)
case Section.controllerOpacity: break
case Section.threeDTouch: break
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
let section = Section(rawValue: section)!
if isSectionHidden(section)
{
return nil
}
else
{
return super.tableView(tableView, titleForHeaderInSection: section.rawValue)
}
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String?
{
let section = Section(rawValue: section)!
if isSectionHidden(section)
{
return nil
}
else
{
return super.tableView(tableView, titleForFooterInSection: section.rawValue)
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
let section = Section(rawValue: section)!
if isSectionHidden(section)
{
return 1
}
else
{
return super.tableView(tableView, heightForHeaderInSection: section.rawValue)
}
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat
{
let section = Section(rawValue: section)!
if isSectionHidden(section)
{
return 1
}
else
{
return super.tableView(tableView, heightForFooterInSection: section.rawValue)
}
}
}

2
External/Roxas vendored

@ -1 +1 @@
Subproject commit 97b3a7ab05ee320d3c96eadc3cc69c38d10ec206
Subproject commit a903b123e1136d77c8b4f4b0e5f78700160f3e97