diff --git a/37-watchkit-game-esp-tester/ESP Tester/ESP Tester.xcodeproj/project.pbxproj b/37-watchkit-game-esp-tester/ESP Tester/ESP Tester.xcodeproj/project.pbxproj index 8d0c22d..5dc2fa5 100644 --- a/37-watchkit-game-esp-tester/ESP Tester/ESP Tester.xcodeproj/project.pbxproj +++ b/37-watchkit-game-esp-tester/ESP Tester/ESP Tester.xcodeproj/project.pbxproj @@ -22,6 +22,8 @@ F3857EB8222E4D27009A33C4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3857EB2222E4D27009A33C4 /* AppDelegate.swift */; }; F3857EBC222E517E009A33C4 /* PhantomFromSpace.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = F3857EBA222E517E009A33C4 /* PhantomFromSpace.mp3 */; }; F3857EBD222E517E009A33C4 /* README.txt in Resources */ = {isa = PBXBuildFile; fileRef = F3857EBB222E517E009A33C4 /* README.txt */; }; + F3857EBF222E5BEB009A33C4 /* CardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3857EBE222E5BEB009A33C4 /* CardViewController.swift */; }; + F3857EC1222E5DD9009A33C4 /* Dimension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3857EC0222E5DD9009A33C4 /* Dimension.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -86,6 +88,8 @@ F3857EB2222E4D27009A33C4 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; F3857EBA222E517E009A33C4 /* PhantomFromSpace.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = PhantomFromSpace.mp3; sourceTree = ""; }; F3857EBB222E517E009A33C4 /* README.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.txt; sourceTree = ""; }; + F3857EBE222E5BEB009A33C4 /* CardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardViewController.swift; sourceTree = ""; }; + F3857EC0222E5DD9009A33C4 /* Dimension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dimension.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -163,6 +167,7 @@ isa = PBXGroup; children = ( F3857EA9222E4D27009A33C4 /* Info.plist */, + F3857EC0222E5DD9009A33C4 /* Dimension.swift */, ); path = "Supporting Files"; sourceTree = ""; @@ -180,6 +185,7 @@ children = ( F3857EAC222E4D27009A33C4 /* HomeViewController.swift */, F3857EAD222E4D27009A33C4 /* Home.storyboard */, + F3857EBE222E5BEB009A33C4 /* CardViewController.swift */, ); path = Home; sourceTree = ""; @@ -347,8 +353,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F3857EC1222E5DD9009A33C4 /* Dimension.swift in Sources */, F3857EB8222E4D27009A33C4 /* AppDelegate.swift in Sources */, F3857EB4222E4D27009A33C4 /* HomeViewController.swift in Sources */, + F3857EBF222E5BEB009A33C4 /* CardViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Controllers/Home/CardViewController.swift b/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Controllers/Home/CardViewController.swift new file mode 100644 index 0000000..3bef220 --- /dev/null +++ b/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Controllers/Home/CardViewController.swift @@ -0,0 +1,79 @@ +// +// CardViewController.swift +// ESP Tester +// +// Created by Brian Sipple on 3/5/19. +// Copyright © 2019 Brian Sipple. All rights reserved. +// + +import UIKit + +class CardViewController: UIViewController { + // MARK: - Instance Properties + + weak var delegate: UIViewController! + + lazy var frontImageView = makeFrontImageView() + lazy var backImageView = makeBackImageView() + + var isCorrect = false + + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + view.bounds = CGRect(x: 0, y: 0, width: Dimension.Card.width, height: Dimension.Card.height) + + view.addSubview(frontImageView) + view.addSubview(backImageView) + + UIView.animate(withDuration: 0.2, animations: { [weak self] in + self?.backImageView.alpha = 1 + }) + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + + + // MARK: - Private functions + + private func makeFrontImageView() -> UIImageView { + let imageView = makeCardImageView() + + imageView.isHidden = true + + return imageView + } + + + private func makeBackImageView() -> UIImageView { + let imageView = makeCardImageView() + + imageView.alpha = 0 + + return imageView + } + + + private func makeCardImageView() -> UIImageView { + guard let image = UIImage(named: "cardBack") else { + fatalError("Failed to load card image assets") + } + + let imageView = UIImageView(image: image) + + return imageView + } +} diff --git a/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Controllers/Home/Home.storyboard b/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Controllers/Home/Home.storyboard index 1eef8b8..1fc3a65 100644 --- a/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Controllers/Home/Home.storyboard +++ b/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Controllers/Home/Home.storyboard @@ -18,9 +18,9 @@ - + - + @@ -34,6 +34,9 @@ + + + diff --git a/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Controllers/Home/HomeViewController.swift b/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Controllers/Home/HomeViewController.swift index 2599b92..b0ede7d 100644 --- a/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Controllers/Home/HomeViewController.swift +++ b/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Controllers/Home/HomeViewController.swift @@ -9,12 +9,100 @@ import UIKit class HomeViewController: UIViewController { - + @IBOutlet var cardContainer: UIView! + + // MARK: - Instance Properties + + var cardViewControllers: [CardViewController] = [] + + lazy var cardPositions = makeCardPositions() + lazy var cardImages = makeCardImages() + + + // MARK: - Lifecycle + override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. + + loadCards() + } + + + // MARK: - Methods + + @objc func loadCards() { + removeCardsInView() + cardImages.shuffle() + + for (index, position) in cardPositions.enumerated() { + let cardViewController = CardViewController() + let cardImage = cardImages[index] + + cardViewController.delegate = self + + // use view controller containment... + addChild(cardViewController) + + // ...AND add the VC's view to our container view + cardContainer.addSubview(cardViewController.view) + cardViewController.didMove(toParent: self) + + cardViewController.view.center = position + cardViewController.frontImageView.image = cardImage + + // if the new card is a star, mark is as "correct" + if cardImage.accessibilityIdentifier == "star" { + cardViewController.isCorrect = true + } + + cardViewControllers.append(cardViewController) + } + } + + func removeCardsInView() { + for card in cardViewControllers { + card.view.removeFromSuperview() + card.removeFromParent() + } + + cardViewControllers.removeAll(keepingCapacity: true) + } + + // MARK: - Private functions + + private func makeCardPositions() -> [CGPoint] { + let cardWidth = Dimension.Card.width + let cardHeight = Dimension.Card.height + let xSpacing = 10 + let ySpacing = 10 + + var positions: [CGPoint] = [] + + for row in 0...1 { + let yPos = (cardHeight / 2) + (cardHeight * row) + (ySpacing * row) + 15 + + positions += [ + CGPoint(x: (cardWidth / 2) + (cardWidth * 0) + (xSpacing * 1) + 15, y: yPos), + CGPoint(x: (cardWidth / 2) + (cardWidth * 1) + (xSpacing * 2) + 15, y: yPos), + CGPoint(x: (cardWidth / 2) + (cardWidth * 2) + (xSpacing * 3) + 15, y: yPos), + CGPoint(x: (cardWidth / 2) + (cardWidth * 3) + (xSpacing * 4) + 15, y: yPos), + ] + } + + return positions + } + + + private func makeCardImages() -> [UIImage] { + return [ + "Circle", "Circle", "Cross", "Cross", "Lines", "Lines", "Square", "Star" + ].map { + let image = UIImage(named: "card\($0)")! + image.accessibilityIdentifier = $0 + + return image + } } - - } diff --git a/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Supporting Files/Dimension.swift b/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Supporting Files/Dimension.swift new file mode 100644 index 0000000..6dd4fa6 --- /dev/null +++ b/37-watchkit-game-esp-tester/ESP Tester/ESP Tester/Supporting Files/Dimension.swift @@ -0,0 +1,22 @@ +// +// Dimension.swift +// ESP Tester +// +// Created by Brian Sipple on 3/5/19. +// Copyright © 2019 Brian Sipple. All rights reserved. +// + +import Foundation + + +enum Dimension { + enum MainView { + static let width = 480 + static let height = 320 + } + + enum Card { + static let width = 100 + static let height = 140 + } +} diff --git a/README.md b/README.md index 457e015..cce7a84 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ | 34 | 🎮
[Four in a Row](/34-gamekit-four-in-a-row) | GameplayKit AI, GKGameModel, GKGameModelPlayer, GKGameModelUpdate, AI Heuristics, NSCopying, GKMinmaxStrategist | ✅ | | 35 | 🛠
[Random Numbers](/35-random-numbers) | Int.random(in:), Float.random(in:), Double.random(in:), CGFloat.random(in:), Bool.random(), arc4random(), GKRandomSource.sharedRandom(), GKLinearCongruentialRandomSource, GKMersenneTwisterRandomSource, GKARC4RandomSource, GKRandomDistribution, GKShuffledDistribution, GKGaussianDistribution, Fisher-Yates Algorithm, arrayByShufflingObjects(in:), the importance of being able to seed the source of randomness 🌱| ✅ | | 36 | 🎮
[Crashy Plane](/36-crashy-plane) | scale modes, parallax scrolling, SpriteKit Physics, SKPhysicsContactDelegate, SKPhysicsBody, SKAudioNode, managing game state with enums, restarting entire game scenes | ✅ | -| 37 | 🎮
[Psychic Tester](/37-psychic-tester) | WatchKit Extensions, 3D Touch, CAEmitterLayer, CAGradientLayer, @IBDesignable, @IBInspectable, transition(with:), WCSession, WKInterfaceLabel, WKInterfaceButton | 🚧 | +| 37 | 🎮
[WatchKit ESP Tester](/37-watchkit-game-esp-tester) | View Controller containment, WatchKit Extensions, 3D Touch, CAEmitterLayer, CAGradientLayer, @IBDesignable, @IBInspectable, transition(with:), WCSession, WKInterfaceLabel, WKInterfaceButton | 🚧 | | 38 | 🛠
[Github Commits (Core Data)](/38-githubcommits) | NSFetchRequest, NSManagedObject, NSPredicate, NSSortDescriptor, NSFetchedResultsController, ISO8601DateFormatter | 🔴 | | 39 | 🛠
[Unit testing with XCTest](/39-swift-unit-tests) | XCTest, `filter()`, Test-Driven Development, Functional Programming, XCTestCase, Setting a Baseline, NSCountedSet, XCUIApplication(), XCUIElementQuery, UI Test Recording | 🔴 |