Adds ability for user to add/edit custom cheats

This commit is contained in:
Riley Testut 2016-05-27 15:25:14 -05:00
parent f0f88a529a
commit f7ed6f75d1
9 changed files with 707 additions and 50 deletions

@ -1 +1 @@
Subproject commit 9e14654abbcf82ff415eb6fb2648b0be5091ca44
Subproject commit a20b36f96e52bc236d243cd8b1b55fc5e1bbf76c

@ -1 +1 @@
Subproject commit 87d9cfa5574fe42db453f9b6b08b3e7190a4b7d2
Subproject commit cac97f68eea3eb85471a16b468ae8da6fa1038e6

View File

@ -25,6 +25,8 @@
BF2A53FD1BB74FC60052BD0C /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; };
BF2A53FE1BB74FC60052BD0C /* ZipZap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */; };
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34FA061CF0F510006624C7 /* EditCheatViewController.swift */; };
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34FA101CF1899D006624C7 /* CheatTextView.swift */; };
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF11C5D7FB000C1184C /* PauseViewController.swift */; };
BF353FF31C5D7FB000C1184C /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF11C5D7FB000C1184C /* PauseViewController.swift */; };
BF353FF61C5D837600C1184C /* PauseMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF353FF41C5D837600C1184C /* PauseMenu.storyboard */; };
@ -139,6 +141,8 @@
BF27CC941BCB7B7A00A20D89 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.0.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; };
BF27CC961BCC890700A20D89 /* GamesCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesCollectionViewController.swift; sourceTree = "<group>"; };
BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesCollectionHeaderView.swift; path = "Pause Menu/Save States/SaveStatesCollectionHeaderView.swift"; sourceTree = "<group>"; };
BF34FA061CF0F510006624C7 /* EditCheatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EditCheatViewController.swift; path = "Pause Menu/Cheats/EditCheatViewController.swift"; sourceTree = "<group>"; };
BF34FA101CF1899D006624C7 /* CheatTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatTextView.swift; path = "Pause Menu/Cheats/CheatTextView.swift"; sourceTree = "<group>"; };
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseViewController.swift; path = "Pause Menu/PauseViewController.swift"; sourceTree = "<group>"; };
BF353FF51C5D837600C1184C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PauseMenu.storyboard; sourceTree = "<group>"; };
BF353FF81C5D870B00C1184C /* PauseItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseItem.swift; path = "Pause Menu/PauseItem.swift"; sourceTree = "<group>"; };
@ -388,6 +392,8 @@
isa = PBXGroup;
children = (
BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */,
BF34FA061CF0F510006624C7 /* EditCheatViewController.swift */,
BF34FA101CF1899D006624C7 /* CheatTextView.swift */,
);
name = Cheats;
sourceTree = "<group>";
@ -730,8 +736,10 @@
BF27CC971BCC890700A20D89 /* GamesCollectionViewController.swift in Sources */,
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
BF7AE81E1C2E984300B1B5BC /* GridCollectionViewCell.swift in Sources */,
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
BF4566E81BC090B6007BFA1A /* Model.xcdatamodeld in Sources */,
BFDE393C1BC0CEDF003F72E8 /* Game.swift in Sources */,
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */,
BFC2731A1BE6152200D22B05 /* GameCollection.swift in Sources */,
BFF1E5641BE04CAF000E9EF6 /* BoxArtImageView.swift in Sources */,
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */,

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Dt0-nV-isV">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Dt0-nV-isV">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
</dependencies>
<scenes>
<!--Pause View Controller-->
@ -226,5 +227,143 @@
</objects>
<point key="canvasLocation" x="1702" y="1377"/>
</scene>
<!--Cheat-->
<scene sceneID="SMZ-Ah-CVb">
<objects>
<tableViewController storyboardIdentifier="editCheatViewController" id="jTR-Oe-YUJ" customClass="EditCheatViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="BV9-ff-x83">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="calibratedRGB"/>
<sections>
<tableViewSection headerTitle="Name" id="QT6-DZ-g70">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="ZeC-rg-QFa">
<rect key="frame" x="0.0" y="113.5" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="ZeC-rg-QFa" id="UIF-fK-ApW">
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Cheat Name" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="DD1-X0-hg7">
<rect key="frame" x="15" y="0.0" width="570" height="43.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<textInputTraits key="textInputTraits" autocapitalizationType="words" autocorrectionType="no" returnKeyType="done"/>
<connections>
<action selector="textFieldDidEndEditing:" destination="jTR-Oe-YUJ" eventType="editingDidEnd" id="RcS-1I-sHp"/>
<action selector="updateCheatName:" destination="jTR-Oe-YUJ" eventType="editingChanged" id="ZMg-n2-rsf"/>
</connections>
</textField>
</subviews>
<constraints>
<constraint firstItem="DD1-X0-hg7" firstAttribute="top" secondItem="UIF-fK-ApW" secondAttribute="top" id="3Ta-uE-hx1"/>
<constraint firstItem="DD1-X0-hg7" firstAttribute="trailing" secondItem="UIF-fK-ApW" secondAttribute="trailingMargin" id="Igk-iU-oK0"/>
<constraint firstItem="DD1-X0-hg7" firstAttribute="leading" secondItem="UIF-fK-ApW" secondAttribute="leadingMargin" id="ZPW-fc-ZhS"/>
<constraint firstAttribute="bottom" secondItem="DD1-X0-hg7" secondAttribute="bottom" id="n6Z-kr-dcR"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Type" footerTitle="Description" id="rvn-VK-2uH">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Tst-zn-e04">
<rect key="frame" x="0.0" y="207.5" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="Tst-zn-e04" id="gwV-zS-RWQ">
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="xrD-ue-96Q">
<rect key="frame" x="15" y="7" width="570" height="29"/>
<segments>
<segment title="First"/>
<segment title="Second"/>
</segments>
<connections>
<action selector="updateCheatType:" destination="jTR-Oe-YUJ" eventType="valueChanged" id="ex4-x7-yaK"/>
</connections>
</segmentedControl>
</subviews>
<constraints>
<constraint firstItem="xrD-ue-96Q" firstAttribute="centerY" secondItem="gwV-zS-RWQ" secondAttribute="centerY" id="HEE-6o-6oQ"/>
<constraint firstItem="xrD-ue-96Q" firstAttribute="leading" secondItem="gwV-zS-RWQ" secondAttribute="leadingMargin" id="e07-ea-lKZ"/>
<constraint firstItem="xrD-ue-96Q" firstAttribute="trailing" secondItem="gwV-zS-RWQ" secondAttribute="trailingMargin" id="qk0-rE-QTg"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Code" footerTitle="Description" id="rHC-nA-ga0">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="210" id="xxc-cz-sb7">
<rect key="frame" x="0.0" y="318" width="600" height="210"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="xxc-cz-sb7" id="agU-SE-fNa">
<rect key="frame" x="0.0" y="0.0" width="600" height="209.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" text="XXXXXXXX YYYYYYYY" translatesAutoresizingMaskIntoConstraints="NO" id="a17-LB-QXD" customClass="CheatTextView" customModule="Delta" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="600" height="209.5"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="26"/>
<textInputTraits key="textInputTraits" autocapitalizationType="allCharacters" autocorrectionType="no" spellCheckingType="no" returnKeyType="done"/>
<connections>
<outlet property="delegate" destination="jTR-Oe-YUJ" id="n4b-FA-7Dd"/>
</connections>
</textView>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="a17-LB-QXD" secondAttribute="bottom" id="3XH-aF-bNE"/>
<constraint firstItem="a17-LB-QXD" firstAttribute="top" secondItem="agU-SE-fNa" secondAttribute="topMargin" id="NxO-eC-sk6"/>
<constraint firstItem="a17-LB-QXD" firstAttribute="leading" secondItem="agU-SE-fNa" secondAttribute="leadingMargin" id="QwM-HU-6cp"/>
<constraint firstAttribute="trailing" secondItem="a17-LB-QXD" secondAttribute="trailing" id="WpC-Va-wtg"/>
<constraint firstItem="a17-LB-QXD" firstAttribute="leading" secondItem="agU-SE-fNa" secondAttribute="leading" id="acf-fI-y13"/>
<constraint firstItem="a17-LB-QXD" firstAttribute="top" secondItem="agU-SE-fNa" secondAttribute="top" id="gcg-A8-DX9"/>
<constraint firstAttribute="bottomMargin" secondItem="a17-LB-QXD" secondAttribute="bottom" id="hex-Ie-1rn"/>
<constraint firstItem="a17-LB-QXD" firstAttribute="trailing" secondItem="agU-SE-fNa" secondAttribute="trailingMargin" id="ilf-Gt-J7Y"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="NxO-eC-sk6"/>
<exclude reference="QwM-HU-6cp"/>
<exclude reference="hex-Ie-1rn"/>
<exclude reference="ilf-Gt-J7Y"/>
</mask>
</variation>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
<connections>
<outlet property="dataSource" destination="jTR-Oe-YUJ" id="IdU-96-UyX"/>
<outlet property="delegate" destination="jTR-Oe-YUJ" id="7X0-hO-jy3"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Cheat" id="zdc-ES-dan">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="MFY-SZ-J7p">
<connections>
<segue destination="d4f-Cp-p2b" kind="unwind" identifier="unwindEditCheatSegue" unwindAction="unwindFromEditCheatViewController:" id="rC5-FR-JoB"/>
</connections>
</barButtonItem>
<barButtonItem key="rightBarButtonItem" style="done" systemItem="save" id="C7k-Kp-CF3">
<connections>
<action selector="saveCheat:" destination="jTR-Oe-YUJ" id="Goi-Ob-IUg"/>
</connections>
</barButtonItem>
</navigationItem>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<connections>
<outlet property="codeTextView" destination="a17-LB-QXD" id="dgZ-U8-doW"/>
<outlet property="nameTextField" destination="DD1-X0-hg7" id="y36-wg-Min"/>
<outlet property="typeSegmentedControl" destination="xrD-ue-96Q" id="wNy-cq-NM2"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="x1S-Un-UKy" userLabel="First Responder" sceneMemberID="firstResponder"/>
<exit id="d4f-Cp-p2b" userLabel="Exit" sceneMemberID="exit"/>
</objects>
<point key="canvasLocation" x="2385" y="1377"/>
</scene>
</scenes>
</document>

View File

@ -342,9 +342,9 @@ extension EmulationViewController: SaveStatesViewControllerDelegate
/// Cheats
extension EmulationViewController: CheatsViewControllerDelegate
{
func cheatsViewControllerActiveGame(cheatsViewController: CheatsViewController) -> Game
func cheatsViewControllerActiveEmulatorCore(saveStatesViewController: CheatsViewController) -> EmulatorCore
{
return self.emulatorCore.game as! Game
return self.emulatorCore
}
func cheatsViewController(cheatsViewController: CheatsViewController, didActivateCheat cheat: Cheat) throws

View File

@ -0,0 +1,166 @@
//
// CheatTextView.swift
// Delta
//
// Created by Riley Testut on 5/22/16.
// Copyright © 2016 Riley Testut. All rights reserved.
//
import UIKit
import CoreText
import DeltaCore
import Roxas
private let CheatPrefixAttribute = "prefix"
class CheatTextView: UITextView
{
var cheatFormat: CheatFormat? {
didSet {
self.updateAttributedFormat()
}
}
@NSCopying private var attributedFormat: NSAttributedString?
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
self.layoutManager.delegate = self
self.textContainer.widthTracksTextView = false
self.textContainer.heightTracksTextView = false
}
}
extension CheatTextView
{
override func layoutSubviews()
{
super.layoutSubviews()
if let format = self.cheatFormat, font = self.font
{
let characterWidth = ("A" as NSString).sizeWithAttributes([NSFontAttributeName: font]).width
let width = characterWidth * CGFloat(format.format.characters.count)
self.textContainer.size = CGSize(width: width, height: 0)
}
}
}
private extension CheatTextView
{
func updateAttributedFormat()
{
guard let format = self.cheatFormat?.format else
{
self.attributedFormat = nil
return
}
let attributedFormat = NSMutableAttributedString()
var prefixString: NSString? = nil
let scanner = NSScanner(string: format)
scanner.charactersToBeSkipped = nil
while (!scanner.atEnd)
{
var string: NSString? = nil
scanner.scanCharactersFromSet(NSCharacterSet.alphanumericCharacterSet(), intoString: &string)
guard let scannedString = string where scannedString.length > 0 else { break }
let attributedString = NSMutableAttributedString(string: scannedString as String)
if let prefixString = prefixString where prefixString.length > 0
{
attributedString.addAttribute(CheatPrefixAttribute, value: prefixString, range: NSRange(location: 0, length: 1))
}
attributedFormat.appendAttributedString(attributedString)
prefixString = nil
scanner.scanUpToCharactersFromSet(NSCharacterSet.alphanumericCharacterSet(), intoString: &prefixString)
}
self.attributedFormat = attributedFormat
rst_dispatch_sync_on_main_thread {
self.setNeedsLayout()
self.layoutIfNeeded()
let range = NSRange(location: 0, length: (self.text as NSString).length)
self.layoutManager.invalidateGlyphsForCharacterRange(range, changeInLength: 0, actualCharacterRange: nil)
self.layoutManager.invalidateLayoutForCharacterRange(range, actualCharacterRange: nil)
self.layoutManager.ensureGlyphsForCharacterRange(range)
self.layoutManager.ensureLayoutForCharacterRange(range)
}
}
}
extension CheatTextView: NSLayoutManagerDelegate
{
func layoutManager(layoutManager: NSLayoutManager, shouldGenerateGlyphs glyphs: UnsafePointer<CGGlyph>, properties props: UnsafePointer<NSGlyphProperty>, characterIndexes charIndexes: UnsafePointer<Int>, font aFont: UIFont, forGlyphRange glyphRange: NSRange) -> Int
{
// Returning 0 = let the layoutManager do the normal logic
guard let attributedFormat = self.attributedFormat else { return 0 }
let glyphCount = glyphRange.length
// Ensure the buffer is long enough to hold our additional glyphs
// If we're only modifying one character, glyphCount * 2 = 2, which is not large enough if we're inserting multiple separator characters
let bufferSize = max(attributedFormat.length + 1, glyphCount * 2)
// Allocate our replacement buffers
let glyphBuffer = UnsafeMutablePointer<CGGlyph>.alloc(bufferSize)
let propertyBuffer = UnsafeMutablePointer<NSGlyphProperty>.alloc(bufferSize)
let characterBuffer = UnsafeMutablePointer<Int>.alloc(bufferSize)
var offset = 0
for i in 0 ..< glyphCount
{
// The index the actual character maps to in the cheat format
let characterIndex = charIndexes[i] % attributedFormat.length
if let prefix = attributedFormat.attributesAtIndex(characterIndex, effectiveRange: nil)[CheatPrefixAttribute] as? String
{
// If there is a prefix string, we insert the glyphs (and associated properties/character indexes) first
let prefixCount = prefix.characters.count
for j in 0 ..< prefixCount
{
characterBuffer[i + offset + j] = charIndexes[i]
propertyBuffer[i + offset + j] = props[i]
// Prepend prefix character
var prefixCharacter = (prefix as NSString).characterAtIndex(0)
CTFontGetGlyphsForCharacters(aFont as CTFont, &prefixCharacter, glyphBuffer + (i + offset + j), 1)
}
offset += prefixCount
}
// Copy over the information from the original buffers
characterBuffer[i + offset] = charIndexes[i]
propertyBuffer[i + offset] = props[i]
glyphBuffer[i + offset] = glyphs[i]
}
// Replace buffers with our own buffers, and ensure length takes into account any added glpyhs
layoutManager.setGlyphs(glyphBuffer, properties: propertyBuffer, characterIndexes: characterBuffer, font: aFont, forGlyphRange: NSRange(location: glyphRange.location, length: glyphCount + offset))
// Clean up memory
characterBuffer.dealloc(bufferSize)
propertyBuffer.dealloc(bufferSize)
glyphBuffer.dealloc(bufferSize)
// Return total number of glyphs
return glyphCount + offset
}
}

View File

@ -15,7 +15,7 @@ import Roxas
protocol CheatsViewControllerDelegate: class
{
func cheatsViewControllerActiveGame(saveStatesViewController: CheatsViewController) -> Game
func cheatsViewControllerActiveEmulatorCore(saveStatesViewController: CheatsViewController) -> EmulatorCore
func cheatsViewController(cheatsViewController: CheatsViewController, didActivateCheat cheat: Cheat) throws
func cheatsViewController(cheatsViewController: CheatsViewController, didDeactivateCheat cheat: Cheat) throws
}
@ -67,13 +67,21 @@ extension CheatsViewController
}
}
//MARK: - Navigation -
private extension CheatsViewController
{
@IBAction func unwindFromEditCheatViewController(segue: UIStoryboardSegue)
{
}
}
//MARK: - Update -
private extension CheatsViewController
{
//MARK: - Update -
func updateFetchedResultsController()
{
let game = self.delegate.cheatsViewControllerActiveGame(self)
let game = self.delegate.cheatsViewControllerActiveEmulatorCore(self).game as! Game
let fetchRequest = Cheat.fetchRequest()
fetchRequest.returnsObjectsAsFaults = false
@ -105,42 +113,8 @@ private extension CheatsViewController
{
@IBAction func addCheat()
{
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
backgroundContext.performBlock {
var game = self.delegate.cheatsViewControllerActiveGame(self)
game = backgroundContext.objectWithID(game.objectID) as! Game
let cheat = Cheat.insertIntoManagedObjectContext(backgroundContext)
cheat.game = game
cheat.name = "Unlimited Jumps"
cheat.code = "3E2C-AF6F"
cheat.type = .gameGenie
do
{
try self.delegate.cheatsViewController(self, didActivateCheat: cheat)
backgroundContext.saveWithErrorLogging()
}
catch EmulatorCore.CheatError.invalid
{
dispatch_async(dispatch_get_main_queue()) {
let alertController = UIAlertController(title: NSLocalizedString("Invalid Cheat", comment: ""), message: NSLocalizedString("Please make sure you typed the cheat code in correctly and try again.", comment: ""), preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .Default, handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
}
print("Invalid cheat:", cheat.name, cheat.code)
}
catch let error as NSError
{
print("Unknown Cheat Error:", error, cheat.name, cheat.code)
}
}
let editCheatViewController = self.makeEditCheatViewController(cheat: nil)
self.presentViewController(RSTContainInNavigationController(editCheatViewController), animated: true, completion: nil)
}
func deleteCheat(cheat: Cheat)
@ -156,8 +130,8 @@ private extension CheatsViewController
}
}
//MARK: - Content -
/// Content
//MARK: - Convenience -
/// Convenience
private extension CheatsViewController
{
func configure(cell cell: UITableViewCell, forIndexPath indexPath: NSIndexPath)
@ -167,6 +141,17 @@ private extension CheatsViewController
cell.textLabel?.font = UIFont.boldSystemFontOfSize(cell.textLabel!.font.pointSize)
cell.accessoryType = cheat.enabled ? .Checkmark : .None
}
func makeEditCheatViewController(cheat cheat: Cheat?) -> EditCheatViewController
{
let editCheatViewController = self.storyboard!.instantiateViewControllerWithIdentifier("editCheatViewController") as! EditCheatViewController
editCheatViewController.delegate = self
editCheatViewController.supportedCheatFormats = self.delegate.cheatsViewControllerActiveEmulatorCore(self).supportedCheatFormats
editCheatViewController.cheat = cheat
editCheatViewController.game = self.delegate.cheatsViewControllerActiveEmulatorCore(self).game as! Game
return editCheatViewController
}
}
extension CheatsViewController
@ -236,13 +221,15 @@ extension CheatsViewController
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]?
{
let cheat = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Cheat
let deleteAction = UITableViewRowAction(style: .Destructive, title: NSLocalizedString("Delete", comment: "")) { (action, indexPath) in
let cheat = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Cheat
self.deleteCheat(cheat)
}
let editAction = UITableViewRowAction(style: .Normal, title: NSLocalizedString("Edit", comment: "")) { (action, indexPath) in
let editCheatViewController = self.makeEditCheatViewController(cheat: cheat)
self.presentViewController(RSTContainInNavigationController(editCheatViewController), animated: true, completion: nil)
}
return [deleteAction, editAction]
@ -254,6 +241,34 @@ extension CheatsViewController
}
}
extension CheatsViewController: EditCheatViewControllerDelegate
{
func editCheatViewController(editCheatViewController: EditCheatViewController, activateCheat cheat: Cheat, previousCheat: Cheat?) throws
{
try self.delegate.cheatsViewController(self, didActivateCheat: cheat)
if let previousCheat = previousCheat
{
let code = cheat.code
previousCheat.managedObjectContext?.performBlockAndWait({
guard previousCheat.code != code else { return }
do
{
try self.delegate.cheatsViewController(self, didDeactivateCheat: previousCheat)
}
catch let error as NSError
{
print(error)
}
})
}
}
}
//MARK: - <NSFetchedResultsControllerDelegate> -
extension CheatsViewController: NSFetchedResultsControllerDelegate
{

View File

@ -0,0 +1,329 @@
//
// EditCheatViewController.swift
// Delta
//
// Created by Riley Testut on 5/21/16.
// Copyright © 2016 Riley Testut. All rights reserved.
//
import UIKit
import CoreData
import DeltaCore
import Roxas
protocol EditCheatViewControllerDelegate: class
{
func editCheatViewController(editCheatViewController: EditCheatViewController, activateCheat cheat: Cheat, previousCheat: Cheat?) throws
}
private extension EditCheatViewController
{
enum ValidationError: ErrorType
{
case invalidCode
case duplicateName
case duplicateCode
}
enum Section: Int
{
case name
case type
case code
}
}
class EditCheatViewController: UITableViewController
{
weak var delegate: EditCheatViewControllerDelegate?
var cheat: Cheat?
var game: Game!
var supportedCheatFormats: [CheatFormat]!
private var selectedCheatFormat: CheatFormat {
let cheatFormat = self.supportedCheatFormats[self.typeSegmentedControl.selectedSegmentIndex]
return cheatFormat
}
private var mutableCheat: Cheat!
private var managedObjectContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
@IBOutlet private var nameTextField: UITextField!
@IBOutlet private var typeSegmentedControl: UISegmentedControl!
@IBOutlet private var codeTextView: CheatTextView!
}
extension EditCheatViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
var name: String!
var type: CheatType!
var code: String!
self.managedObjectContext.performBlockAndWait {
// Main Thread context is read-only, so we either create a new cheat, or get a reference to the current cheat in a new background context
if let cheat = self.cheat
{
self.mutableCheat = self.managedObjectContext.objectWithID(cheat.objectID) as? Cheat
}
else
{
self.mutableCheat = Cheat.insertIntoManagedObjectContext(self.managedObjectContext)
self.mutableCheat.game = self.managedObjectContext.objectWithID(self.game.objectID) as! Game
self.mutableCheat.type = self.supportedCheatFormats.first!.type
self.mutableCheat.code = ""
self.mutableCheat.name = ""
}
self.mutableCheat.enabled = true // After we save a cheat, it should be enabled
name = self.mutableCheat.name
type = self.mutableCheat.type
code = self.mutableCheat.code.sanitized(characterSet: self.selectedCheatFormat.allowedCodeCharacters)
}
// Update UI
if name.characters.count == 0
{
self.title = NSLocalizedString("Cheat", comment: "")
}
else
{
self.title = name
}
self.nameTextField.text = name
self.codeTextView.text = code
self.typeSegmentedControl.removeAllSegments()
for (index, format) in self.supportedCheatFormats.enumerate()
{
self.typeSegmentedControl.insertSegmentWithTitle(format.name, atIndex: index, animated: false)
}
if let index = self.supportedCheatFormats.indexOf({ $0.type == type })
{
self.typeSegmentedControl.selectedSegmentIndex = index
}
else
{
self.typeSegmentedControl.selectedSegmentIndex = 0
}
self.updateCheatType(self.typeSegmentedControl)
self.updateSaveButtonState()
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
if let superview = self.codeTextView.superview
{
let layoutMargins = superview.layoutMargins
self.codeTextView.textContainerInset = layoutMargins
self.codeTextView.textContainer.lineFragmentPadding = 0
}
if self.appearing
{
self.nameTextField.becomeFirstResponder()
}
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
private extension EditCheatViewController
{
@IBAction func updateCheatName(sender: UITextField)
{
var title = sender.text ?? ""
if title.characters.count == 0
{
title = NSLocalizedString("Cheat", comment: "")
}
self.title = title
self.updateSaveButtonState()
}
@IBAction func updateCheatType(sender: UISegmentedControl)
{
self.codeTextView.cheatFormat = self.selectedCheatFormat
UIView.performWithoutAnimation {
self.tableView.reloadSections(NSIndexSet(index: Section.type.rawValue), withRowAnimation: .None)
// Hacky-ish workaround so we can update the footer text for the code section without causing text view to resign first responder status
self.tableView.beginUpdates()
if let footerView = self.tableView.footerViewForSection(Section.code.rawValue)
{
footerView.textLabel!.text = self.tableView(self.tableView, titleForFooterInSection: Section.code.rawValue)
footerView.sizeToFit()
}
self.tableView.endUpdates()
}
}
func updateSaveButtonState()
{
let isValidName = !(self.nameTextField.text ?? "").isEmpty
let isValidCode = !self.codeTextView.text.isEmpty
self.navigationItem.rightBarButtonItem?.enabled = isValidName && isValidCode
}
@IBAction func saveCheat(sender: UIBarButtonItem)
{
self.mutableCheat.managedObjectContext?.performBlockAndWait {
self.mutableCheat.name = self.nameTextField.text ?? ""
self.mutableCheat.type = self.selectedCheatFormat.type
self.mutableCheat.code = self.codeTextView.text.formatted(cheatFormat: self.selectedCheatFormat)
do
{
try self.validateCheat(self.mutableCheat)
self.mutableCheat.managedObjectContext?.saveWithErrorLogging()
self.performSegueWithIdentifier("unwindEditCheatSegue", sender: sender)
}
catch ValidationError.invalidCode
{
self.presentErrorAlert(title: NSLocalizedString("Invalid Code", comment: ""), message: NSLocalizedString("Please make sure you typed the cheat code in correctly and try again.", comment: "")) {
self.codeTextView.becomeFirstResponder()
}
}
catch ValidationError.duplicateCode
{
self.presentErrorAlert(title: NSLocalizedString("Duplicate Code", comment: ""), message: NSLocalizedString("A cheat already exists with this code. Please type in a different code and try again.", comment: "")) {
self.codeTextView.becomeFirstResponder()
}
}
catch ValidationError.duplicateName
{
self.presentErrorAlert(title: NSLocalizedString("Duplicate Name", comment: ""), message: NSLocalizedString("A cheat already exists with this name. Please rename this cheat and try again.", comment: "")) {
self.nameTextField.becomeFirstResponder()
}
}
catch let error as NSError
{
print(error)
}
}
}
func validateCheat(cheat: Cheat) throws
{
let name = cheat.name!
let code = cheat.code
// Find all cheats that are for the same game, don't have the same identifier as the current cheat, but have either the same name or code
let predicate = NSPredicate(format: "%K == %@ AND %K != %@ AND (%K == %@ OR %K == %@)", Cheat.Attributes.game.rawValue, cheat.game, Cheat.Attributes.identifier.rawValue, cheat.identifier, Cheat.Attributes.code.rawValue, code, Cheat.Attributes.name.rawValue, name)
let cheats = Cheat.instancesWithPredicate(predicate, inManagedObjectContext: self.managedObjectContext, type: Cheat.self)
for cheat in cheats
{
if cheat.name == name
{
throw ValidationError.duplicateName
}
else if cheat.code == code
{
throw ValidationError.duplicateCode
}
}
do
{
try self.delegate?.editCheatViewController(self, activateCheat: cheat, previousCheat: self.cheat)
}
catch
{
throw ValidationError.invalidCode
}
}
@IBAction func textFieldDidEndEditing(sender: UITextField)
{
sender.resignFirstResponder()
}
func presentErrorAlert(title title: String, message: String, handler: (Void -> Void)?)
{
dispatch_async(dispatch_get_main_queue()) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .Default, handler: { action in
handler?()
}))
self.presentViewController(alertController, animated: true, completion: nil)
}
}
}
extension EditCheatViewController
{
override func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String?
{
switch Section(rawValue: section)!
{
case .name: return super.tableView(tableView, titleForFooterInSection: section)
case .type:
let title = String.localizedStringWithFormat("Code format is %@.", self.selectedCheatFormat.format)
return title
case .code:
let containsSpaces = self.selectedCheatFormat.format.containsString(" ")
let containsDashes = self.selectedCheatFormat.format.containsString("-")
switch (containsSpaces, containsDashes)
{
case (true, false): return NSLocalizedString("Spaces will be inserted automatically as you type.", comment: "")
case (false, true): return NSLocalizedString("Dashes will be inserted automatically as you type.", comment: "")
case (true, true): return NSLocalizedString("Spaces and dashes will be inserted automatically as you type.", comment: "")
case (false, false): return NSLocalizedString("Code will be formatted automatically as you type.", comment: "")
}
}
}
}
extension EditCheatViewController: UITextViewDelegate
{
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool
{
defer { self.updateSaveButtonState() }
guard text != "\n" else
{
textView.resignFirstResponder()
return false
}
let sanitizedText = text.sanitized(characterSet: self.selectedCheatFormat.allowedCodeCharacters)
guard sanitizedText != text else { return true }
textView.textStorage.replaceCharactersInRange(range, withString: sanitizedText)
return false
}
}

2
External/Roxas vendored

@ -1 +1 @@
Subproject commit c795d0ace22cfe57c3b73613630e14b58d2f8f09
Subproject commit 17cc7b771ca819cdad14c05ee9cd0529d5df1c91