Merge branch 'feature/custom_artwork' into develop

This commit is contained in:
Riley Testut 2017-05-02 16:46:23 -07:00
commit a288f564b6
18 changed files with 731 additions and 351 deletions

View File

@ -70,6 +70,12 @@
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF5E7F451B9A652600AE44F8 /* Settings.storyboard */; };
BF6866171DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */; };
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF696B7F1D9B2B02009639E0 /* Theme.swift */; };
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3121EB7E47F008E83CD /* ImportOption.swift */; };
BF6BF3181EB82111008E83CD /* iTunesImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3171EB82111008E83CD /* iTunesImportOption.swift */; };
BF6BF31A1EB82146008E83CD /* ClipboardImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3191EB82146008E83CD /* ClipboardImportOption.swift */; };
BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */; };
BF6BF3211EB82362008E83CD /* GamesDatabase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF6BF31F1EB82362008E83CD /* GamesDatabase.storyboard */; };
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3261EB87EB8008E83CD /* PhotoLibraryImportOption.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, ); }; };
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */; };
@ -174,7 +180,7 @@
BF5942841E09BC8B0051894B /* _GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _GameCollection.swift; path = Database/Model/Machine/_GameCollection.swift; sourceTree = "<group>"; };
BF5942851E09BC8B0051894B /* _SaveState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _SaveState.swift; path = Database/Model/Machine/_SaveState.swift; sourceTree = "<group>"; };
BF59428B1E09BC930051894B /* ControllerSkinConfigurations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ControllerSkinConfigurations.h; path = Database/Model/Misc/ControllerSkinConfigurations.h; sourceTree = "<group>"; };
BF59428D1E09BCFB0051894B /* ImportController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImportController.swift; path = Components/Importing/ImportController.swift; sourceTree = "<group>"; };
BF59428D1E09BCFB0051894B /* ImportController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImportController.swift; path = Importing/ImportController.swift; sourceTree = "<group>"; };
BF59428F1E09BD1A0051894B /* NSFetchedResultsController+Conveniences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFetchedResultsController+Conveniences.h"; sourceTree = "<group>"; };
BF5942901E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFetchedResultsController+Conveniences.m"; sourceTree = "<group>"; };
BF5942911E09BD1A0051894B /* NSManagedObject+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Conveniences.swift"; sourceTree = "<group>"; };
@ -185,6 +191,12 @@
BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ControllerSkin+Configuring.swift"; sourceTree = "<group>"; };
BF696B7F1D9B2B02009639E0 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Theme.swift; path = Theming/Theme.swift; sourceTree = "<group>"; };
BF6BB2451BB73FE800CCF94A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
BF6BF3121EB7E47F008E83CD /* ImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImportOption.swift; path = Importing/ImportOption.swift; sourceTree = "<group>"; };
BF6BF3171EB82111008E83CD /* iTunesImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = iTunesImportOption.swift; path = "Importing/Import Options/iTunesImportOption.swift"; sourceTree = "<group>"; };
BF6BF3191EB82146008E83CD /* ClipboardImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ClipboardImportOption.swift; path = "Importing/Import Options/ClipboardImportOption.swift"; sourceTree = "<group>"; };
BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabaseImportOption.swift; path = "Importing/Import Options/GamesDatabaseImportOption.swift"; sourceTree = "<group>"; };
BF6BF3201EB82362008E83CD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/GamesDatabase.storyboard; 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; };
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>"; };
@ -203,7 +215,7 @@
BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesStoryboardSegue.swift; path = Segues/SaveStatesStoryboardSegue.swift; sourceTree = "<group>"; };
BFEC732C1AAECC4A00650035 /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBCDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InputStreamOutputWriter.swift; path = Components/Importing/InputStreamOutputWriter.swift; sourceTree = "<group>"; };
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InputStreamOutputWriter.swift; path = Database/InputStreamOutputWriter.swift; sourceTree = "<group>"; };
BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameMetadataTableViewCell.swift; path = Database/OpenVGDB/GameMetadataTableViewCell.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>"; };
@ -304,7 +316,6 @@
isa = PBXGroup;
children = (
BF5942581E09BB810051894B /* Action.swift */,
BF59428C1E09BCE50051894B /* Importing */,
BF5942671E09BBB70051894B /* Collection View */,
BF5942601E09BBA80051894B /* Loading */,
);
@ -333,6 +344,7 @@
isa = PBXGroup;
children = (
BF59426D1E09BC5D0051894B /* DatabaseManager.swift */,
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */,
BF5942711E09BC690051894B /* Model */,
BF95E2751E49763D0030E7AD /* OpenVGDB */,
);
@ -386,7 +398,8 @@
isa = PBXGroup;
children = (
BF59428D1E09BCFB0051894B /* ImportController.swift */,
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */,
BF6BF3121EB7E47F008E83CD /* ImportOption.swift */,
BF6BF3161EB820F4008E83CD /* Import Options */,
);
name = Importing;
sourceTree = "<group>";
@ -399,6 +412,17 @@
name = Theming;
sourceTree = "<group>";
};
BF6BF3161EB820F4008E83CD /* Import Options */ = {
isa = PBXGroup;
children = (
BF6BF3171EB82111008E83CD /* iTunesImportOption.swift */,
BF6BF3191EB82146008E83CD /* ClipboardImportOption.swift */,
BF6BF3261EB87EB8008E83CD /* PhotoLibraryImportOption.swift */,
BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */,
);
name = "Import Options";
sourceTree = "<group>";
};
BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */ = {
isa = PBXGroup;
children = (
@ -434,6 +458,7 @@
BF95E2751E49763D0030E7AD /* OpenVGDB */ = {
isa = PBXGroup;
children = (
BF6BF31F1EB82362008E83CD /* GamesDatabase.storyboard */,
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */,
BF95E2761E4977BF0030E7AD /* GameMetadata.swift */,
BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */,
@ -522,6 +547,7 @@
BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */,
BFAA1FEB1B8AA4E800495943 /* Settings */,
BF59426C1E09BC450051894B /* Database */,
BF59428C1E09BCE50051894B /* Importing */,
BF930FFB1EB6D6EC00E8DBA0 /* Systems */,
BF5942571E09BB5D0051894B /* Components */,
BF696B7E1D9B2AE6009639E0 /* Theming */,
@ -657,6 +683,7 @@
buildActionMask = 2147483647;
files = (
BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */,
BF6BF3211EB82362008E83CD /* GamesDatabase.storyboard in Resources */,
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */,
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */,
BF02D5DA1DDEBB3000A5E131 /* openvgdb.sqlite in Resources */,
@ -750,6 +777,7 @@
buildActionMask = 2147483647;
files = (
BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */,
BF6BF3181EB82111008E83CD /* iTunesImportOption.swift in Sources */,
BF59427C1E09BC830051894B /* Cheat.swift in Sources */,
BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */,
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */,
@ -761,6 +789,7 @@
BF59428E1E09BCFB0051894B /* ImportController.swift in Sources */,
BF930FFD1EB6D6FF00E8DBA0 /* System.swift in Sources */,
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */,
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */,
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */,
BFFC46201D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift in Sources */,
BF5942731E09BC700051894B /* Model.xcdatamodel in Sources */,
@ -775,6 +804,7 @@
BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */,
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */,
BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */,
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */,
BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */,
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */,
@ -806,11 +836,13 @@
BF5942861E09BC8B0051894B /* _Cheat.swift in Sources */,
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */,
BF5942931E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m in Sources */,
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */,
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */,
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */,
BF59425C1E09BB810051894B /* Action.swift in Sources */,
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */,
BF7AE8081C2E858400B1B5BC /* PauseMenuViewController.swift in Sources */,
BF6BF31A1EB82146008E83CD /* ClipboardImportOption.swift in Sources */,
BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */,
BF59427F1E09BC830051894B /* GameCollection.swift in Sources */,
);
@ -827,6 +859,14 @@
name = PauseMenu.storyboard;
sourceTree = "<group>";
};
BF6BF31F1EB82362008E83CD /* GamesDatabase.storyboard */ = {
isa = PBXVariantGroup;
children = (
BF6BF3201EB82362008E83CD /* Base */,
);
name = GamesDatabase.storyboard;
sourceTree = "<group>";
};
BFFA71E01AAC406100EE9DD1 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (

View File

@ -0,0 +1,107 @@
<?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="6bq-zy-UZU">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Games Database-->
<scene sceneID="S7I-gw-igt">
<objects>
<tableViewController id="SB6-jW-dhZ" customClass="GamesDatabaseBrowserViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="97" sectionHeaderHeight="28" sectionFooterHeight="28" id="bJf-Sa-ZOX">
<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"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Games Database" id="rwF-kd-avR">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="BnB-5n-Rff">
<connections>
<segue destination="mUU-ug-yNs" kind="unwind" unwindAction="unwindFromGamesDatabaseBrowserWith:" id="zdg-Az-WwQ"/>
</connections>
</barButtonItem>
</navigationItem>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="f3a-hX-Qnu" userLabel="First Responder" sceneMemberID="firstResponder"/>
<exit id="mUU-ug-yNs" userLabel="Exit" sceneMemberID="exit"/>
</objects>
<point key="canvasLocation" x="2652" y="1001.649175412294"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="REv-V5-eEz">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="6bq-zy-UZU" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="uzY-vR-coL">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="SB6-jW-dhZ" kind="relationship" relationship="rootViewController" id="b0w-Fq-hrk"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Hr9-N6-XXA" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1854" y="1002"/>
</scene>
</scenes>
<resources>
<image name="BoxArt" width="100" height="100"/>
</resources>
</document>

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="11762" systemVersion="16C68" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="SPq-Bk-fQl">
<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="SPq-Bk-fQl">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -49,86 +48,11 @@
</connections>
</barButtonItem>
</navigationItem>
<connections>
<segue destination="6bq-zy-UZU" kind="presentation" identifier="gamesDatabaseBrowser" id="7TT-mP-bjt"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="JYx-xE-nis" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1036" y="1002"/>
</scene>
<!--Games Database-->
<scene sceneID="S7I-gw-igt">
<objects>
<tableViewController id="SB6-jW-dhZ" customClass="GamesDatabaseBrowserViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="97" sectionHeaderHeight="28" sectionFooterHeight="28" id="bJf-Sa-ZOX">
<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"/>
<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"/>
<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"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Games Database" id="rwF-kd-avR">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="BnB-5n-Rff">
<connections>
<segue destination="mUU-ug-yNs" kind="unwind" unwindAction="unwindFromGamesDatabaseBrowserWith:" id="zdg-Az-WwQ"/>
</connections>
</barButtonItem>
</navigationItem>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="f3a-hX-Qnu" userLabel="First Responder" sceneMemberID="firstResponder"/>
<exit id="mUU-ug-yNs" userLabel="Exit" sceneMemberID="exit"/>
</objects>
<point key="canvasLocation" x="2652" y="1001.649175412294"/>
</scene>
<!--Game Collection View Controller-->
<scene sceneID="qNA-NP-TiF">
<objects>
@ -161,7 +85,6 @@
<connections>
<segue destination="X2o-q6-XD5" kind="unwind" identifier="unwindFromGames" unwindAction="unwindFromGamesViewControllerWith:" id="k8C-Xn-maU"/>
<segue destination="MPk-bF-nkj" kind="presentation" identifier="saveStates" customClass="SaveStatesStoryboardSegue" customModule="Delta" customModuleProvider="target" id="1Xp-2J-0cq"/>
<segue destination="6bq-zy-UZU" kind="presentation" identifier="gamesDatabaseBrowser" id="mzX-Bb-MaX"/>
</connections>
</collectionViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bW1-t8-idm" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -314,31 +237,11 @@
</objects>
<point key="canvasLocation" x="3409" y="1716"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="REv-V5-eEz">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="6bq-zy-UZU" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" misplaced="YES" barStyle="black" id="uzY-vR-coL">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="SB6-jW-dhZ" kind="relationship" relationship="rootViewController" id="b0w-Fq-hrk"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Hr9-N6-XXA" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1854" y="1002"/>
</scene>
</scenes>
<resources>
<image name="BoxArt" width="100" height="100"/>
<image name="Settings_Button" width="22" height="22"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="mzX-Bb-MaX"/>
<segue reference="Tey-6Z-UHp"/>
</inferredMetricsTieBreakers>
</document>

View File

@ -1,184 +0,0 @@
//
// ImportController.swift
// Delta
//
// Created by Riley Testut on 10/10/15.
// Copyright © 2015 Riley Testut. All rights reserved.
//
import UIKit
import ObjectiveC
import DeltaCore
import MobileCoreServices
protocol ImportControllerDelegate
{
func importController(_ importController: ImportController, didImport games: Set<Game>, with errors: Set<DatabaseManager.ImportError>)
func importController(_ importController: ImportController, didImport controllerSkins: Set<ControllerSkin>, with errors: Set<DatabaseManager.ImportError>)
/** Optional **/
func importControllerDidCancel(_ importController: ImportController)
}
extension ImportControllerDelegate
{
func importControllerDidCancel(_ importController: ImportController)
{
// Empty Implementation
}
}
class ImportController: NSObject
{
var delegate: ImportControllerDelegate?
fileprivate weak var presentingViewController: UIViewController?
fileprivate func presentImportController(from presentingViewController: UIViewController, animated: Bool, completion: ((Void) -> Void)?)
{
self.presentingViewController = presentingViewController
var documentTypes = System.supportedSystems.map { $0.gameType.rawValue }
documentTypes.append(kUTTypeDeltaControllerSkin as String)
documentTypes.append(kUTTypeZipArchive as String)
// Add GBA4iOS's exported UTIs in case user has GBA4iOS installed (which may override Delta's UTI declarations)
documentTypes.append("com.rileytestut.gba")
documentTypes.append("com.rileytestut.gbc")
documentTypes.append("com.rileytestut.gb")
#if os(iOS)
let documentMenuController = UIDocumentMenuViewController(documentTypes: documentTypes, in: .import)
documentMenuController.delegate = self
documentMenuController.addOption(withTitle: NSLocalizedString("iTunes", comment: ""), image: nil, order: .first) { self.importFromiTunes(nil) }
self.presentingViewController?.present(documentMenuController, animated: true, completion: nil)
#else
self.importFromiTunes(completion)
#endif
}
private func importFromiTunes(_ completion: ((Void) -> Void)?)
{
let alertController = UIAlertController(title: NSLocalizedString("Import from iTunes?", comment: ""), message: NSLocalizedString("Delta will import the games and controller skins copied over via iTunes.", comment: ""), preferredStyle: .alert)
let importAction = UIAlertAction(title: NSLocalizedString("Import", comment: ""), style: .default) { action in
let documentsDirectoryURL = DatabaseManager.defaultDirectoryURL().deletingLastPathComponent()
do
{
let contents = try FileManager.default.contentsOfDirectory(at: documentsDirectoryURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
DatabaseManager.shared.performBackgroundTask { (context) in
let controllerSkinURLs = contents.filter { $0.pathExtension.lowercased() == "deltaskin" }
self.importControllerSkins(at: Set(controllerSkinURLs))
let gameURLs = contents.filter { GameType(fileExtension: $0.pathExtension) != nil || $0.pathExtension.lowercased() == "zip" }
self.importGames(at: Set(gameURLs))
}
}
catch let error as NSError
{
print(error)
}
self.presentingViewController?.importController = nil
}
alertController.addAction(importAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { action in
self.delegate?.importControllerDidCancel(self)
self.presentingViewController?.importController = nil
}
alertController.addAction(cancelAction)
self.presentingViewController?.present(alertController, animated: true, completion: completion)
}
fileprivate func importGames(at urls: Set<URL>)
{
DatabaseManager.shared.importGames(at: urls) { (games, errors) in
self.delegate?.importController(self, didImport: games, with: errors)
}
}
fileprivate func importControllerSkins(at urls: Set<URL>)
{
DatabaseManager.shared.importControllerSkins(at: urls) { (controllerSkins, errors) in
self.delegate?.importController(self, didImport: controllerSkins, with: errors)
}
}
}
#if os(iOS)
extension ImportController: UIDocumentMenuDelegate
{
func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController)
{
documentPicker.delegate = self
self.presentingViewController?.present(documentPicker, animated: true, completion: nil)
}
func documentMenuWasCancelled(_ documentMenu: UIDocumentMenuViewController)
{
self.delegate?.importControllerDidCancel(self)
self.presentingViewController?.importController = nil
}
}
extension ImportController: UIDocumentPickerDelegate
{
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL)
{
if url.pathExtension.lowercased() == "deltaskin"
{
self.importControllerSkins(at: [url])
}
else
{
self.importGames(at: [url])
}
self.presentingViewController?.importController = nil
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController)
{
self.delegate?.importControllerDidCancel(self)
self.presentingViewController?.importController = nil
}
}
#endif
private var ImportControllerKey: UInt8 = 0
extension UIViewController
{
fileprivate(set) var importController: ImportController?
{
set
{
objc_setAssociatedObject(self, &ImportControllerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get
{
return objc_getAssociatedObject(self, &ImportControllerKey) as? ImportController
}
}
func present(_ importController: ImportController, animated: Bool, completion: ((Void) -> Void)?)
{
self.importController = importController
importController.presentImportController(from: self, animated: animated, completion: completion)
}
}

View File

@ -74,14 +74,12 @@ class LoadImageURLOperation: RSTLoadOperation<UIImage, NSURL>
private func loadLocalImage(completion: @escaping (UIImage?, Error?) -> Void)
{
let options: NSDictionary = [kCGImageSourceShouldCache as NSString: true]
guard let imageSource = CGImageSourceCreateWithURL(self.url as CFURL, options) else {
guard let imageSource = CGImageSourceCreateWithURL(self.url as CFURL, nil) else {
completion(nil, .doesNotExist)
return
}
guard let quartzImage = CGImageSourceCreateImageAtIndex(imageSource, 0, options) else {
guard let quartzImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
completion(nil, .invalid)
return
}

View File

@ -53,7 +53,6 @@ extension DatabaseManager
case (.saveFailed, _): return false
}
}
}
}
@ -494,6 +493,14 @@ extension DatabaseManager
return gameTypeDirectoryURL
}
class func artworkURL(for game: Game) -> URL
{
let gameURL = game.fileURL
let artworkURL = gameURL.deletingPathExtension().appendingPathExtension("jpg")
return artworkURL
}
}
//MARK: - Private -

View File

@ -22,6 +22,37 @@ public class Game: _Game, GameProtocol
return fileURL
}
public override var artworkURL: URL? {
get {
self.willAccessValue(forKey: #keyPath(Game.artworkURL))
var artworkURL = self.primitiveValue(forKey: #keyPath(Game.artworkURL)) as? URL
self.didAccessValue(forKey: #keyPath(Game.artworkURL))
if let unwrappedArtworkURL = artworkURL, unwrappedArtworkURL.isFileURL
{
// Recreate the stored URL relative to current sandbox location.
artworkURL = URL(fileURLWithPath: unwrappedArtworkURL.relativePath, relativeTo: DatabaseManager.gamesDirectoryURL)
}
return artworkURL
}
set {
self.willChangeValue(forKey: #keyPath(Game.artworkURL))
var artworkURL = newValue
if let newValue = newValue, newValue.isFileURL
{
// Store a relative URL, since the sandbox location changes.
artworkURL = URL(fileURLWithPath: newValue.lastPathComponent, relativeTo: DatabaseManager.gamesDirectoryURL)
}
self.setPrimitiveValue(artworkURL, forKey: #keyPath(Game.artworkURL))
self.didChangeValue(forKey: #keyPath(Game.artworkURL))
}
}
}
extension Game

View File

@ -7,6 +7,7 @@
//
import UIKit
import MobileCoreServices
import DeltaCore
@ -47,10 +48,14 @@ class GameCollectionViewController: UICollectionViewController
fileprivate let dataSource = RSTFetchedResultsCollectionViewDataSource<Game>(fetchedResultsController: NSFetchedResultsController())
fileprivate let prototypeCell = GridCollectionViewCell()
fileprivate let imageOperationQueue = RSTOperationQueue()
fileprivate let imageCache = NSCache<NSURL, UIImage>()
fileprivate var _performing3DTouchTransition = false
fileprivate weak var _destination3DTouchTransitionViewController: UIViewController?
fileprivate var _renameAction: UIAlertAction?
fileprivate var _changingArtworkGame: Game?
}
//MARK: - UIViewController -
@ -122,25 +127,7 @@ extension GameCollectionViewController
saveStatesViewController.game = game
saveStatesViewController.mode = .loading
saveStatesViewController.theme = self.theme
case "gamesDatabaseBrowser":
let game = sender as! Game
let gamesDatabaseBrowserViewController = (segue.destination as! UINavigationController).topViewController as! GamesDatabaseBrowserViewController
gamesDatabaseBrowserViewController.selectionHandler = { (metadata) in
DatabaseManager.shared.performBackgroundTask({ (context) in
let temporaryGame = context.object(with: game.objectID) as! Game
temporaryGame.artworkURL = metadata.artworkURL
context.saveWithErrorLogging()
DispatchQueue.main.async {
gamesDatabaseBrowserViewController.dismiss(animated: true, completion: nil)
}
})
}
case "unwindFromGames":
let destinationViewController = segue.destination as! GameViewController
let cell = sender as! UICollectionViewCell
@ -222,28 +209,28 @@ private extension GameCollectionViewController
cell.isImageViewVibrancyEnabled = true
}
cell.imageView.image = #imageLiteral(resourceName: "BoxArt")
cell.maximumImageSize = CGSize(width: 90, height: 90)
cell.textLabel.text = game.name
cell.textLabel.textColor = UIColor.gray
if let artworkURL = game.artworkURL, !ignoreImageOperations
{
cell.imageView.sd_setImage(with: artworkURL, placeholderImage: #imageLiteral(resourceName: "BoxArt"), options: .continueInBackground) { (image, error, type, url) in
let imageOperation = LoadImageURLOperation(url: artworkURL)
imageOperation.resultsCache = self.imageCache
imageOperation.resultHandler = { (image, error) in
if let error = error
if let image = image
{
print(error)
}
if image != nil
{
cell.isImageViewVibrancyEnabled = false
DispatchQueue.main.async {
cell.imageView.image = image
cell.isImageViewVibrancyEnabled = false
}
}
}
}
else
{
cell.imageView.image = #imageLiteral(resourceName: "BoxArt")
self.imageOperationQueue.addOperation(imageOperation, forKey: indexPath as NSCopying)
}
}
@ -356,7 +343,16 @@ private extension GameCollectionViewController
func changeArtwork(for game: Game)
{
self.performSegue(withIdentifier: "gamesDatabaseBrowser", sender: game)
self._changingArtworkGame = game
let clipboardImportOption = ClipboardImportOption()
let photoLibraryImportOption = PhotoLibraryImportOption(presentingViewController: self)
let gamesDatabaseImportOption = GamesDatabaseImportOption(presentingViewController: self)
let importController = ImportController(documentTypes: [kUTTypeImage as String])
importController.delegate = self
importController.importOptions = [clipboardImportOption, photoLibraryImportOption, gamesDatabaseImportOption]
self.present(importController, animated: true, completion: nil)
}
func share(_ game: Game)
@ -493,6 +489,94 @@ extension GameCollectionViewController: SaveStatesViewControllerDelegate
}
}
//MARK: - ImportControllerDelegate -
/// ImportControllerDelegate
extension GameCollectionViewController: ImportControllerDelegate
{
func importController(_ importController: ImportController, didImportItemsAt urls: Set<URL>)
{
guard let game = self._changingArtworkGame else { return }
var imageURL: URL?
if let url = urls.first
{
if url.isFileURL
{
do
{
let imageData = try Data(contentsOf: url)
if let image = UIImage(data: imageData)
{
let resizedImage = image.resizing(toFit: CGSize(width: 300, height: 300))
if let resizedData = UIImageJPEGRepresentation(resizedImage, 0.85)
{
let destinationURL = DatabaseManager.artworkURL(for: game)
try resizedData.write(to: destinationURL, options: .atomic)
imageURL = destinationURL
}
}
}
catch
{
print(error)
}
}
else
{
imageURL = url
}
}
if let imageURL = imageURL
{
if let previousArtworkURL = game.artworkURL as NSURL?
{
// Remove previous artwork from cache.
self.imageCache.removeObject(forKey: previousArtworkURL)
}
DatabaseManager.shared.performBackgroundTask { (context) in
let temporaryGame = context.object(with: game.objectID) as! Game
temporaryGame.artworkURL = imageURL
context.saveWithErrorLogging()
DispatchQueue.main.async {
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
}
}
else
{
func presentAlertController()
{
let alertController = UIAlertController(title: NSLocalizedString("Unable to Change Artwork", comment: ""), message: NSLocalizedString("The image might be corrupted or in an unsupported format.", comment: ""), preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("OK"), style: .cancel, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
if let presentedViewController = self.presentedViewController
{
presentedViewController.dismiss(animated: true) {
presentAlertController()
}
}
else
{
presentAlertController()
}
}
}
func importControllerDidCancel(_ importController: ImportController)
{
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
}
//MARK: - UICollectionViewDelegate -
/// UICollectionViewDelegate
extension GameCollectionViewController
@ -545,8 +629,8 @@ extension GameCollectionViewController
override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
{
let cell = cell as! GridCollectionViewCell
cell.imageView.sd_cancelCurrentImageLoad()
let operation = self.imageOperationQueue[indexPath as NSCopying]
operation?.cancel()
}
}

View File

@ -8,6 +8,7 @@
import UIKit
import CoreData
import MobileCoreServices
import DeltaCore
@ -266,37 +267,50 @@ extension GamesViewController: ImportControllerDelegate
{
@IBAction fileprivate func importFiles()
{
let importController = ImportController()
var documentTypes = Set(System.supportedSystems.map { $0.gameType.rawValue })
documentTypes.insert(kUTTypeZipArchive as String)
// Add GBA4iOS's exported UTIs in case user has GBA4iOS installed (which may override Delta's UTI declarations)
documentTypes.insert("com.rileytestut.gba")
documentTypes.insert("com.rileytestut.gbc")
documentTypes.insert("com.rileytestut.gb")
let itunesImportOption = iTunesImportOption(presentingViewController: self)
let importController = ImportController(documentTypes: documentTypes)
importController.delegate = self
importController.importOptions = [itunesImportOption]
self.present(importController, animated: true, completion: nil)
}
//MARK: - ImportControllerDelegate
@nonobjc func importController(_ importController: ImportController, didImport games: Set<Game>, with errors: Set<DatabaseManager.ImportError>)
func importController(_ importController: ImportController, didImportItemsAt urls: Set<URL>)
{
if errors.count > 0
{
let alertController = UIAlertController.alertController(for: .games, with: errors)
self.present(alertController, animated: true, completion: nil)
let gameURLs = urls.filter { $0.pathExtension.lowercased() != "deltaskin" }
DatabaseManager.shared.importGames(at: Set(gameURLs)) { (games, errors) in
if errors.count > 0
{
let alertController = UIAlertController.alertController(for: .games, with: errors)
self.present(alertController, animated: true, completion: nil)
}
if games.count > 0
{
print("Imported Games:", games.map { $0.name })
}
}
if games.count > 0
{
print("Imported Games:", games.map { $0.name })
}
}
@nonobjc func importController(_ importController: ImportController, didImport controllerSkins: Set<ControllerSkin>, with errors: Set<DatabaseManager.ImportError>)
{
if errors.count > 0
{
let alertController = UIAlertController.alertController(for: .controllerSkins, with: errors)
self.present(alertController, animated: true, completion: nil)
}
if controllerSkins.count > 0
{
print("Imported Controller Skins:", controllerSkins.map { $0.name })
let controllerSkinURLs = urls.filter { $0.pathExtension.lowercased() == "deltaskin" }
DatabaseManager.shared.importControllerSkins(at: Set(controllerSkinURLs)) { (controllerSkins, errors) in
if errors.count > 0
{
let alertController = UIAlertController.alertController(for: .controllerSkins, with: errors)
self.present(alertController, animated: true, completion: nil)
}
if controllerSkins.count > 0
{
print("Imported Controller Skins:", controllerSkins.map { $0.name })
}
}
}
}

View File

@ -0,0 +1,39 @@
//
// ClipboardImportOption.swift
// Delta
//
// Created by Riley Testut on 5/1/17.
// Copyright © 2017 Riley Testut. All rights reserved.
//
import UIKit
import MobileCoreServices
import Roxas
struct ClipboardImportOption: ImportOption
{
let title = NSLocalizedString("Clipboard", comment: "")
let image: UIImage? = nil
func `import`(withCompletionHandler completionHandler: @escaping (Set<URL>?) -> Void)
{
guard UIPasteboard.general.hasImages else { return completionHandler([]) }
guard let data = UIPasteboard.general.data(forPasteboardType: kUTTypeImage as String) else { return completionHandler([]) }
do
{
let temporaryURL = FileManager.uniqueTemporaryURL()
try data.write(to: temporaryURL, options: .atomic)
completionHandler([temporaryURL])
}
catch
{
print(error)
completionHandler([])
}
}
}

View File

@ -0,0 +1,42 @@
//
// GamesDatabaseImportOption.swift
// Delta
//
// Created by Riley Testut on 5/1/17.
// Copyright © 2017 Riley Testut. All rights reserved.
//
import UIKit
struct GamesDatabaseImportOption: ImportOption
{
let title = NSLocalizedString("Games Database", comment: "")
let image: UIImage? = nil
private let presentingViewController: UIViewController
init(presentingViewController: UIViewController)
{
self.presentingViewController = presentingViewController
}
func `import`(withCompletionHandler completionHandler: @escaping (Set<URL>?) -> Void)
{
let storyboard = UIStoryboard(name: "GamesDatabase", bundle: nil)
let navigationController = (storyboard.instantiateInitialViewController() as! UINavigationController)
let gamesDatabaseBrowserViewController = navigationController.topViewController as! GamesDatabaseBrowserViewController
gamesDatabaseBrowserViewController.selectionHandler = { (metadata) in
if let artworkURL = metadata.artworkURL
{
completionHandler([artworkURL])
}
else
{
completionHandler(nil)
}
}
self.presentingViewController.present(navigationController, animated: true, completion: nil)
}
}

View File

@ -0,0 +1,60 @@
//
// PhotoLibraryImportOption.swift
// Delta
//
// Created by Riley Testut on 5/2/17.
// Copyright © 2017 Riley Testut. All rights reserved.
//
import UIKit
import MobileCoreServices
class PhotoLibraryImportOption: NSObject, ImportOption
{
let title = NSLocalizedString("Photo Library", comment: "")
let image: UIImage? = nil
private let presentingViewController: UIViewController
fileprivate var completionHandler: ((Set<URL>?) -> Void)?
init(presentingViewController: UIViewController)
{
self.presentingViewController = presentingViewController
super.init()
}
func `import`(withCompletionHandler completionHandler: @escaping (Set<URL>?) -> Void)
{
self.completionHandler = completionHandler
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
imagePickerController.sourceType = .photoLibrary
imagePickerController.mediaTypes = [kUTTypeImage as String]
self.presentingViewController.present(imagePickerController, animated: true, completion: nil)
}
}
extension PhotoLibraryImportOption: UIImagePickerControllerDelegate, UINavigationControllerDelegate
{
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage, let data = UIImageJPEGRepresentation(image, 0.85) else {
self.completionHandler?([])
return
}
do
{
let temporaryURL = FileManager.uniqueTemporaryURL()
try data.write(to: temporaryURL, options: .atomic)
self.completionHandler?([temporaryURL])
}
catch
{
self.completionHandler?([])
}
}
}

View File

@ -0,0 +1,73 @@
//
// iTunesImportOption.swift
// Delta
//
// Created by Riley Testut on 5/1/17.
// Copyright © 2017 Riley Testut. All rights reserved.
//
import UIKit
import DeltaCore
struct iTunesImportOption: ImportOption
{
let title = NSLocalizedString("iTunes", comment: "")
let image: UIImage? = nil
private let presentingViewController: UIViewController
init(presentingViewController: UIViewController)
{
self.presentingViewController = presentingViewController
}
func `import`(withCompletionHandler completionHandler: @escaping (Set<URL>?) -> Void)
{
let alertController = UIAlertController(title: NSLocalizedString("Import from iTunes?", comment: ""), message: NSLocalizedString("Delta will import the games and controller skins copied over via iTunes.", comment: ""), preferredStyle: .alert)
let importAction = UIAlertAction(title: NSLocalizedString("Import", comment: ""), style: .default) { action in
var importedURLs = Set<URL>()
let documentsDirectoryURL = DatabaseManager.defaultDirectoryURL().deletingLastPathComponent()
do
{
let contents = try FileManager.default.contentsOfDirectory(at: documentsDirectoryURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
let itemURLs = contents.filter { GameType(fileExtension: $0.pathExtension) != nil || $0.pathExtension.lowercased() == "zip" || $0.pathExtension.lowercased() == "deltaskin" }
for url in itemURLs
{
let destinationURL = FileManager.uniqueTemporaryURL().appendingPathExtension(url.pathExtension)
do
{
try FileManager.default.moveItem(at: url, to: destinationURL)
importedURLs.insert(destinationURL)
}
catch
{
print("Error importing file at URL", url, error)
}
}
}
catch
{
print(error)
}
completionHandler(importedURLs)
}
alertController.addAction(importAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { action in
completionHandler(nil)
}
alertController.addAction(cancelAction)
self.presentingViewController.present(alertController, animated: true, completion: nil)
}
}

View File

@ -0,0 +1,135 @@
//
// ImportController.swift
// Delta
//
// Created by Riley Testut on 10/10/15.
// Copyright © 2015 Riley Testut. All rights reserved.
//
import UIKit
import ObjectiveC
import DeltaCore
import MobileCoreServices
protocol ImportControllerDelegate
{
func importController(_ importController: ImportController, didImportItemsAt urls: Set<URL>)
/** Optional **/
func importControllerDidCancel(_ importController: ImportController)
}
extension ImportControllerDelegate
{
func importControllerDidCancel(_ importController: ImportController)
{
// Empty Implementation
}
}
class ImportController: NSObject
{
let documentTypes: Set<String>
var delegate: ImportControllerDelegate?
var importOptions: [ImportOption]?
init(documentTypes: Set<String>)
{
self.documentTypes = documentTypes
super.init()
}
fileprivate weak var presentingViewController: UIViewController?
fileprivate func presentImportController(from presentingViewController: UIViewController, animated: Bool, completionHandler: ((Void) -> Void)?)
{
self.presentingViewController = presentingViewController
let documentMenuController = UIDocumentMenuViewController(documentTypes: Array(self.documentTypes), in: .import)
documentMenuController.delegate = self
if let reversedImportOptions = self.importOptions?.reversed()
{
for importOption in reversedImportOptions
{
documentMenuController.add(importOption, order: UIDocumentMenuOrder.first) { (urls) in
if let urls = urls
{
self.delegate?.importController(self, didImportItemsAt: urls)
}
else
{
self.delegate?.importControllerDidCancel(self)
}
self.presentingViewController?.importController = nil
}
}
}
self.presentingViewController?.present(documentMenuController, animated: true, completion: nil)
}
}
extension ImportController: UIDocumentMenuDelegate
{
func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController)
{
documentPicker.delegate = self
self.presentingViewController?.present(documentPicker, animated: true, completion: nil)
}
func documentMenuWasCancelled(_ documentMenu: UIDocumentMenuViewController)
{
self.delegate?.importControllerDidCancel(self)
self.presentingViewController?.importController = nil
}
}
extension ImportController: UIDocumentPickerDelegate
{
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL)
{
self.delegate?.importController(self, didImportItemsAt: [url])
self.presentingViewController?.importController = nil
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController)
{
self.delegate?.importControllerDidCancel(self)
self.presentingViewController?.importController = nil
}
}
private var ImportControllerKey: UInt8 = 0
extension UIViewController
{
fileprivate(set) var importController: ImportController?
{
set
{
objc_setAssociatedObject(self, &ImportControllerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get
{
return objc_getAssociatedObject(self, &ImportControllerKey) as? ImportController
}
}
func present(_ importController: ImportController, animated: Bool, completion: ((Void) -> Void)?)
{
self.importController = importController
importController.presentImportController(from: self, animated: animated, completionHandler: completion)
}
}

View File

@ -0,0 +1,29 @@
//
// ImportOption.swift
// Delta
//
// Created by Riley Testut on 5/1/17.
// Copyright © 2017 Riley Testut. All rights reserved.
//
import UIKit
import DeltaCore
extension UIDocumentMenuViewController
{
func add(_ importOption: ImportOption, order: UIDocumentMenuOrder, completionHandler: @escaping (Set<URL>?) -> Void)
{
self.addOption(withTitle: importOption.title, image: importOption.image, order: order) {
importOption.import(withCompletionHandler: completionHandler)
}
}
}
protocol ImportOption
{
var title: String { get }
var image: UIImage? { get }
func `import`(withCompletionHandler completionHandler: @escaping (Set<URL>?) -> Void)
}

View File

@ -126,6 +126,8 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSPhotoLibraryUsageDescription</key>
<string>Press &quot;OK&quot; to allow Delta to use images from your Photo Library as game artwork.</string>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>

View File

@ -16,7 +16,7 @@ extension GameType
{
init?(fileExtension: String)
{
switch fileExtension
switch fileExtension.lowercased()
{
case "smc", "sfc", "fig": self = .snes
case "gba": self = .gba