Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions CheatCodes.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'CheatCodes'
s.version = '0.2.1'
s.version = '0.3.0'
s.summary = 'UIKeyCommand shortcuts for debugging applications in the simulator'

s.description = <<-DESC
Expand All @@ -14,8 +14,6 @@ Available Cheat Codes:
⌘ + ⇧ + ^ + ↓: Trigger restorable state preservation
⇧ + ^ + d: Print documents directory path
⇧ + ^ + e: Re-enable user interaction
⌘ + ⌥ + f: Reset all first run screens
⇧ + ^ + g: Log in a default user account
⇧ + ^ + h: Print the list of available commands
⇧ + ^ + i: Print general device info
⇧ + ^ + l: Print autolayout backtrace
Expand All @@ -36,8 +34,7 @@ DESC
s.source_files = 'CheatCodes/Classes/**/*'

s.pod_target_xcconfig = {
'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'CHEAT_${CONFIGURATION}',
'SWIFT_VERSION' => '3.0'
}
'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'CHEATS_${CONFIGURATION}',
}

end
26 changes: 26 additions & 0 deletions CheatCodes/Classes/CheatCodeCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,34 @@ import UIKit

/// A descriptor for a CheatCodeCommand that wraps a UIKeyCommand
public struct CheatCodeCommand {
/** A single-character string constant that will trigger this command when
pressed along with `modifierFlags`

- note: Also supports these constants:
- UIKeyInputUpArrow
- UIKeyInputDownArrow
- UIKeyInputLeftArrow
- UIKeyInputRightArrow
- UIKeyInputEscape
*/
public let input: String

/** A set of `UIKeyModifierFlags` that must be help along with `input` to
trigger the `UIKeyCommand`

- note: The current default is `[.control, .shift]`
- note: When using the iOS simulator, `.option` may cause issues due to
being used as part of the "Pinch" gesture support. Suggested flags
are: `[.control, .shift]`, `[.control, .command]`
and `[.control, .command, .shift]`
*/
public let modifierFlags: UIKeyModifierFlags

/// The `#selector` to call when the key command is activated
public let action: Selector

/// A description used in an on-screen overlay (on iPad) and in the default
/// help output for `CheatCodes` in the debug console
public let discoverabilityTitle: String


Expand All @@ -28,6 +53,7 @@ public struct CheatCodeCommand {
}

internal extension CheatCodeCommand {

func toKeyCommand() -> UIKeyCommand {
if #available(iOS 9.0, *) {
return UIKeyCommand(input: input, modifierFlags: modifierFlags, action: action, discoverabilityTitle: discoverabilityTitle)
Expand Down
40 changes: 40 additions & 0 deletions CheatCodes/Classes/CheatCodeResponder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
Protocol that indicates a `UIViewController` has opted in to using `CheatCodes`

- requires: Must call `addCheatCodes()` in `viewDidLoad` method to install
`UIKeyCommands`
- warning: you are strongly encouraged to wrap your extension in an `#if` block
so that it will not be compiled for your app store releases.

#if CHEATS_ENABLED
extension MyViewController: CheatCodeResponder { ... }
#endif
*/
public protocol CheatCodeResponder: class {
var cheatCodes: [CheatCodeCommand] { get }
}

/** Extenstion to trigger installing `UIKeyCommand`s from `CheatCodeCommands` defined
in the `UIViewController`'s `cheatCodes` computed var.

- seealso: CheatCodeResponder
*/
public extension UIViewController {
#if CHEATS_Release
/// :nodoc:
@available(iOS 9.0, *)
func addCheatCodes() { /* Don't do anything in Release builds */ }
#else
/**
Add the cheat codes defined by this ViewController's `cheatCodes` computed var
- requires: conformance to `CheatCodeResponder`
*/
@available(iOS 9.0, *)
func addCheatCodes() {
guard let viewController = self as? CheatCodeResponder else { return }
viewController.cheatCodes.forEach { code in
addKeyCommand(code.toKeyCommand())
}
}
#endif
}
24 changes: 22 additions & 2 deletions CheatCodes/Classes/CheatCodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ fileprivate extension Cheat {
}

// MARK: - Cheat Codes Public Infterface
/// CheatCodes additions
/**
Cheat code extensions for `UIKeyCommand` -- adding them here privately makes them
accessible when installing them at the `AppDelegate` level.
*/
extension UIKeyCommand {

#if CHEATS_Release
Expand Down Expand Up @@ -58,7 +61,6 @@ extension UIKeyCommand {
contents(&formatter)
formatter.printContents()
}

}

// MARK: - Cheat Codes Private Interface
Expand Down Expand Up @@ -87,6 +89,24 @@ internal extension UIKeyCommand {
formatter.addKey(" \($0.keyCombo)", value: $0.discoverabilityTitle)
}
}

guard let window = UIApplication.shared.keyWindow else { return }

let bottomResponder = window.perform(Selector(("_deepestUnambiguousResponder"))).takeUnretainedValue() as? UIResponder
var next: UIResponder? = bottomResponder?.next
while next != nil {
if let cheater = next as? CheatCodeResponder {
let cheaterType = type(of: cheater)
tableFormatted(title: "\(cheaterType) Cheat Codes") { formatter2 in
cheater.cheatCodes.sorted(by: { (c1, c2) -> Bool in
c1.input <= c2.input
}).forEach {
formatter2.addKey(" \($0.keyCombo)", value: $0.discoverabilityTitle)
}
}
}
next = next!.next
}
}

/// Toggles the tint adjustment mode between `automatic` and `dimmed` on the key window
Expand Down
10 changes: 8 additions & 2 deletions CheatCodes/Classes/FormattedKeyValuePrinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import Foundation
the title, and the dictionary with the keys right aligned to the longest key.
*/
public struct FormattedKeyValuePrinter {
/// A title for the generated output
public let title: String
var keyValuePairs = [(String,String)]()
var maxKeyLength = 0

private var keyValuePairs = [(String,String)]()
private var maxKeyLength = 0

init(title: String) {
self.title = title
}

/** Add a key and value to the formatted output
- parameter key: a descriptor for the value shown
- parameter value: the relevant piece of data being described
*/
public mutating func addKey(_ key: String, value: String?) {
maxKeyLength = max(maxKeyLength, key.characters.count)
keyValuePairs.append((key,value ?? "(NO VALUE)"))
Expand Down
2 changes: 1 addition & 1 deletion CheatCodes/Classes/UIKeyModifierFlags.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import UIKit

extension UIKeyModifierFlags {
internal extension UIKeyModifierFlags {

func printableKeys() -> String {
return [
Expand Down
54 changes: 49 additions & 5 deletions Example/CheatCodes/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,25 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="vXZ-lx-hvc">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1004" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="yVr-ks-YoS">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11161"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="ufC-wZ-h7g">
<objects>
<viewController id="vXZ-lx-hvc" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
<viewController id="vXZ-lx-hvc" customClass="ViewController" customModule="CheatCodes_Example" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="jyV-Pf-zRb"/>
<viewControllerLayoutGuide type="bottom" id="2fi-mo-0CV"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="kh9-bI-dsS">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<navigationItem key="navigationItem" id="as5-NX-Qdb">
<barButtonItem key="rightBarButtonItem" title="Item" id="d3z-al-NOf">
<connections>
<segue destination="0qe-Gc-tya" kind="show" id="Y1G-Fd-t79"/>
</connections>
</barButtonItem>
</navigationItem>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="x5A-6p-PRh" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="654" y="162"/>
</scene>
<!--View Controller-->
<scene sceneID="Pl2-Q2-Crb">
<objects>
<viewController id="0qe-Gc-tya" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="CpK-dr-s2t"/>
<viewControllerLayoutGuide type="bottom" id="7O5-h4-PwK"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="w11-Au-qVb">
<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"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="nHa-dJ-EHn" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1374" y="162"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="o0f-B0-NE8">
<objects>
<navigationController id="yVr-ks-YoS" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" id="U4F-Hr-hJV">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="vXZ-lx-hvc" kind="relationship" relationship="rootViewController" id="7Fk-cX-2ZT"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="x55-Nx-7Ne" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-167" y="163"/>
</scene>
</scenes>
</document>
31 changes: 27 additions & 4 deletions Example/CheatCodes/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,41 @@
//

import UIKit
import CheatCodes

class ViewController: UIViewController {

let firstRunKey = "showedFirstRunView"

var hasSeenFirstTimeView: Bool {
get { return UserDefaults.standard.bool(forKey: firstRunKey) }
set { UserDefaults.standard.set(hasSeenFirstTimeView, forKey: firstRunKey) }
}

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if !hasSeenFirstTimeView {
showFirstTimeView()
}

// We need to `opt in` somewhere
if #available(iOS 9.0, *) {
addCheatCodes()
}
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
func showFirstTimeView() {
hasSeenFirstTimeView = true
// Add a view over the whole main view
print("Gonna show a view!")
}

}

extension ViewController: CheatCodeResponder {
var cheatCodes: [CheatCodeCommand] {
return [
CheatCodeCommand(input: UIKeyInputUpArrow, modifierFlags: [.control,.command], action: #selector(showFirstTimeView), discoverabilityTitle: "Show the 'first time' screen")
]
}
}
6 changes: 6 additions & 0 deletions Example/Pods/Pods.xcodeproj/project.pbxproj

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,32 @@ class AppDelegate {
}
```

### Adding Custom Commands
### Adding Custom Commands (to a view controller)

```swift
import UIKit
import CheatCodes
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

// When compiled for a release build, this method does nothing
addCheatCodes()
}
}

#if CHEATS_ENABLED
extension MyViewController: CheatCodeResponder {
var cheatCodes: [CheatCodeCommand] {
return [
CheatCodeCommand(input: UIKeyInputUpArrow, modifierFlags: [.control,.command], action: #selector(showFirstTimeView), discoverabilityTitle: "Show the 'first time' screen")
]
}
}
#endif
```

### Adding Custom Commands (global)

```swift
#if CHEATS_ENABLED
Expand Down