Вступление
С момента их появления в Xcode 6 вместе со Swift до их текущей итерации в Xcode 7.3.1 игровые площадки прошли долгий путь. С новыми функциями и улучшенной стабильностью они превращаются в жизнеспособный инструмент для быстрого прототипирования или быстрого взлома доказательства концепции.
Как разработчик, иногда у вас появляется вдохновение в виде интересной идеи для приложения, и вы хотите быстро написать прототип, который представляет собой суть вашей идеи. Или вы просто хотите проверить свое понимание того, как будет вести себя часть кода UIKit. Если вы похожи на меня, вы бы предпочли избежать хлопот и умственных затрат при создании проекта Xcode и необходимости учитывать множество факторов, таких как типы устройств и разрешения, а также настройки сборки. Эти решения могут быть отложены до тех пор, пока вы не решите, что основная идея заслуживает реализации.
В этом уроке мы создадим карточную игру памяти в пределах игровой площадки. Это обычная, хорошо известная игра, так что здесь нет ничего оригинального. Игра состоит из восьми пар одинаковых карт (всего 16 карт), положенных лицом вниз в сетку 4х4.
Игроку необходимо перевернуть две карты, лица которых кратко раскрываются, а затем быстро переворачиваются. Цель игры состоит в том, чтобы игрок попытался запомнить положения карт и найти идентичные пары, которые затем удаляются из игры. Игра заканчивается, когда сетка очищается.
Игра основана на касании и включает в себя простые анимации просмотра. Вы узнаете, как вы можете вносить изменения в свое приложение, и увидите результат ваших изменений вживую.
1. Начало работы
Запустите Xcode и выберите New> Playground … из меню Xcode File . Дайте игровой площадке имя, например MemoryGameXCPTut , установите Platform на iOS и сохраните игровую площадку. Я использую Xcode 7.3.1 для этого урока.
Найти свой путь вокруг детской площадки
Давайте потратим некоторое время на ознакомление с интерфейсом детской площадки. Не стесняйтесь просматривать этот раздел, если вы уже знакомы с игровыми площадками.
У игровой площадки может быть несколько страниц, каждая из которых связана с собственным представлением в реальном времени и собственными папками источников / ресурсов. Мы не будем использовать несколько страниц в этом руководстве. Игровые площадки поддерживают форматирование разметки, которое позволяет добавлять расширенный текст на игровую площадку и создавать ссылки между страницами игровой площадки.
Первое, что вы видите после создания игровой площадки — это редактор исходных текстов игровой площадки. Здесь вы пишете код, который немедленно влияет на отображение в реальном времени. Один из способов переключить (отключить) внешний вид Project Navigator — использовать ярлык Command-0 . В Навигаторе проектов вы можете видеть две папки, « Источники» и « Ресурсы» .
источники
В папке « Sources » вы можете добавить вспомогательный код в один или несколько файлов Swift, таких как пользовательские классы, контроллеры представлений и представления. Несмотря на то, что основная часть кода, который определяет логику вашего прототипа, присутствует в нем, он является вспомогательным в том смысле, что он скрыт в фоновом режиме при просмотре приложения в режиме реального времени.
Преимущество размещения вспомогательного кода в папке Sources заключается в том, что он автоматически компилируется при каждом изменении и сохранении файла. Таким образом, вы получаете более быструю обратную связь в режиме реального времени от изменений, сделанных на игровой площадке. Вернувшись на игровую площадку, вы можете получить доступ к общим свойствам и методам, которые вы раскрываете во вспомогательном коде, влияющем на поведение вашего приложения.
Ресурсы
Вы можете добавить внешние ресурсы, например изображения, в папку « Ресурсы ».
В этом руководстве вам часто нужно переходить между файлом Swift, который мы создаем в папке Sources, и файлом площадки (технически это также файл Swift, за исключением того, что вы не будете ссылаться на него по имени файла). Мы также используем помощника редактора в учебном пособии, в котором он отображает временную шкалу , чтобы просматривать прямой вывод с кодом игровой площадки. Любые изменения, которые вы делаете на игровой площадке, отражаются мгновенно (ну, в течение нескольких секунд) в прямом эфире. Вы также можете взаимодействовать с интерактивным представлением и его элементами пользовательского интерфейса. Чтобы убедиться, что вы можете сделать все это, взгляните на рисунок ниже.
В соответствии с зелеными числами я добавил к рисунку:
- Эта кнопка скрывает Assistant Editor, так что виден только главный редактор.
- Эта кнопка отображает помощника редактора . Помощник редактора отображается справа от главного редактора. Этот редактор может помочь, показывая нам соответствующие файлы, такие как аналог файла в главном редакторе.
- Слева направо, эти две кнопки соответственно используются для переключения внешнего вида навигатора проекта и консоли отладки. В консоли мы можем проверить вывод операторов print среди прочего.
- Панель перехода в верхней части главного редактора также может использоваться для перехода к определенному файлу. Дважды нажав название проекта, вы вернетесь на игровую площадку. Кроме того, вы также можете использовать Навигатор проекта .
Иногда при просмотре игровой площадки необходимо убедиться, что помощник редактора отображает временную шкалу вместо какого-либо другого файла. На рисунке ниже показано, как это сделать. В помощнике редактора выберите « Временная шкала» , аналог игровой площадки, вместо « Вручную» , что позволяет отображать любой файл в помощнике редактора .
Когда вы редактируете исходный файл из папки Sources , в качестве его аналога, помощник-редактор показывает интерфейс вашего кода, то есть объявления и прототипы функций без их реализации. Я предпочитаю скрывать помощник редактора, когда я работаю с файлом в папке « Источники », и открываю только помощника редактора на площадке, чтобы увидеть его в режиме реального времени.
Чтобы получить доступ к особым способностям игровых площадок, вам необходимо импортировать модуль XCPlayground.
1
|
import XCPlayground
|
Вы устанавливаете свойство XCPlaygroundPage
объекта XCPlaygroundPage
для объекта, который соответствует протоколу XCPlaygroundLiveViewable
. Это может быть пользовательский класс или экземпляр UIView
или UIViewController
.
Добавление файлов в папку источников / ресурсов
Я добавил несколько изображений, с которыми мы можем работать в этом уроке. Загрузите изображения , распакуйте архив и добавьте изображения из папки « Изображения» в папку « Ресурсы » детской площадки в Project Navigator .
Не забудьте перетащить только изображения, чтобы каждый файл изображения находился в папке « Ресурсы », а не в « Ресурсах / изображениях» .
Удалить код на детской площадке. Щелкните правой кнопкой мыши папку Sources и выберите в меню « Новый файл» . Установите имя файла в Game.swift.
2. Написание вспомогательных классов и методов
Добавьте следующий код в Game.swift . Обязательно сохраняйте файл после каждого добавления кода.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import UIKit
import XCPlayground
import GameplayKit // (1)
public extension UIImage { // (2)
public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
color.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let cgImage = image.CGImage else { return nil }
self.init(CGImage: cgImage)
}
}
let cardWidth = CGFloat(120) // (3)
let cardHeight = CGFloat(141)
public class Card: UIImageView { // (4)
public let x: Int
public let y: Int
public init(image: UIImage?, x: Int, y: Int) {
self.x = x
self.y = y
super.init(image: image)
self.backgroundColor = .grayColor()
self.layer.cornerRadius = 10.0
self.userInteractionEnabled = true
}
required public init?(coder aDecoder: NSCoder) {
fatalError(«init(coder:) has not been implemented»)
}
}
|
Я добавил несколько пронумерованных комментариев, чтобы объяснить некоторые разделы реализации:
- В дополнение к
UIKit
иXCPlayground
мы также импортируемGamePlayKit
. Эта структура включает в себя удобный метод, который поможет нам реализовать метод случайного перемешивания массива. - Это расширение в
UIImage
позволяет нам с помощью методовUIKit
создавать изображения сплошного цвета любого размера, который мы хотим. Мы будем использовать это, чтобы установить начальное фоновое изображение игральных карт. - Константы
cardHeight
иcardWidth
представляют размеры изображения карты, на основе которых мы будем вычислять другие размеры. - Класс
Card
, унаследованный отUIImageView
, представляет собой карту. Несмотря на то, что мы установили несколько свойств в классеCard
, основная цель создания этого класса состоит в том, чтобы помочь нам идентифицировать и перебирать подпредставления, которые соответствуют игральным картам в игре. Карты также имеют свойстваx
иy
чтобы запомнить их положение в сетке.
3. Просмотр контроллера
Добавьте следующий код в Game.swift сразу после предыдущего кода:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
public class GameController: UIViewController {
// (1): public variables so we can manipulate them in the playground
public var padding = CGFloat(20)/* {
didSet {
resetGrid()
}
} */
public var backImage: UIImage = UIImage(
color: .redColor(),
size: CGSize(width: cardWidth, height: cardHeight))!
// (2): computed properties
var viewWidth: CGFloat {
get {
return 4 * cardWidth + 5 * padding
}
}
var viewHeight: CGFloat {
get {
return 4 * cardHeight + 5 * padding
}
}
var shuffledNumbers = [Int]() // stores shuffled card numbers
// var firstCard: Card?
public init() {
super.init(nibName: nil, bundle: nil)
preferredContentSize = CGSize(width: viewWidth, height: viewHeight)
shuffle()
setupGrid()
// uncomment later:
// let tap = UITapGestureRecognizer(target: self, action: #selector(GameController.handleTap(_:)))
// view.addGestureRecognizer(tap)
}
required public init?(coder aDecoder: NSCoder) {
fatalError(«init(coder:) has not been implemented»)
}
public override func loadView() {
view = UIView()
view.backgroundColor = .blueColor()
view.frame = CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight)
}
// (3): Using GameplayKit API to generate a shuffling of the array [1, 1, 2, 2, …, 8, 8]
func shuffle() {
let numbers = (1…8).flatMap{[$0, $0]}
shuffledNumbers =
GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(numbers) as!
}
// (4): Convert from card position on grid to index in the shuffled card numbers array
func cardNumberAt(x: Int, _ y: Int) -> Int {
assert(0 <= x && x < 4 && 0 <= y && y < 4)
return shuffledNumbers[4 * x + y]
}
// (5): Position of card’s center in superview
func centerOfCardAt(x: Int, _ y: Int) -> CGPoint {
assert(0 <= x && x < 4 && 0 <= y && y < 4)
let (w, h) = (cardWidth + padding, cardHeight + padding)
return CGPoint(
x: CGFloat(x) * w + w/2 + padding/2,
y: CGFloat(y) * h + h/2 + padding/2)
}
// (6): setup the subviews
func setupGrid() {
for i in 0..<4 {
for j in 0..<4 {
let n = cardNumberAt(i, j)
let card = Card(image: UIImage(named: String(n)), x: i, y: j)
card.tag = n
card.center = centerOfCardAt(i, j)
view.addSubview(card)
}
}
}
// (7): reset grid
/*
func resetGrid() {
view.frame = CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight)
for v in view.subviews {
if let card = v as?
card.center = centerOfCardAt(card.x, card.y)
}
}
}
*/
override public func viewDidAppear(animated: Bool) {
for v in view.subviews {
if let card = v as?
UIView.transitionWithView(
card,
duration: 1.0,
options: .TransitionFlipFromLeft,
animations: {
card.image = self.backImage
}, completion: nil)
}
}
}
}
|
- Два свойства,
padding
иbackImage
, объявленыpublic
чтобы мы могли получить к ним доступ позже. Они представляют пустое пространство вокруг карточек на сетке и изображение, отображаемое на обороте каждой карточки соответственно. Обратите внимание, что обоим свойствам были присвоены начальные значения, представляющие отступ 20 и сплошной красный цвет для изображения на стороне карты, не являющейся стороной. Вы можете игнорировать закомментированный код сейчас. - Мы рассчитываем желаемую ширину и высоту видов с помощью вычисленных свойств. Чтобы понять вычисление
viewWidth
, помните, что в каждой строке по четыре карты, и нам также необходимо учитывать отступ каждой карты. Та же идея применима к вычислениюviewHeight
. - Код
(1...8).flatMap{[$0, $0]}
является кратким способом создания массива[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8]
. Если вы не знакомы с функциональным программированием, вы также можете написатьfor
-loop для генерации массива. Используя методы из инфраструктурыGamePlayKit
, мы перетасовываем числа в массиве. Номера соответствуют восьми парам карт. Каждое число представляет изображение карты с тем же именем (например, значение1
вshuffledArray
соответствует 1.png ). - Мы написали метод, который отображает местоположение карты в сетке 4×4 на ее местоположение в массиве
shuffledNumbers
длиной 16. Коэффициент4
в арифметическом расчете отражает тот факт, что у нас есть четыре карты в строке. - У нас также есть метод, который вычисляет положение карты (ее
center
свойство) в сетке на основе размеров карты и заполнения. - Метод
setupGrid()
вызывается во время инициализации контроллера представления. Выкладывает сетку 4х4. Он также назначает идентификатор каждой карты на основе массиваshuffledNumbers
и сохраняет его в свойствеtag
унаследованном от базового класса карты,UIView
. В игровой логике мы сравниваем значенияtag
чтобы определить, совпадают ли две карты или нет. Эта довольно элементарная схема моделирования служит достаточно хорошо для наших текущих потребностей. - Этот в настоящее время неиспользуемый фрагмент кода поможет нам переставить карты в случае изменения заполнения. Помните, что мы объявили свойство
padding
как общедоступное, чтобы иметь к нему доступ на игровой площадке. - Код в
viewDidAppear(_:)
запускается сразу после того, как представление контроллера представления становится видимым. Мы перебираем подпредставления представления, и, если подпредставление является экземпляром классаCard
(проверяется с помощью оператора asableable downcasting), телоif
-statement определяет переход, который необходимо выполнить. Здесь мы изменим изображение, отображаемое на карточках, с изображения мультфильма, определяющего лицо каждой карточки, до (общего)backImage
всех карточек. Этот переход сопровождается анимацией переворота слева направо, которая создает видимость физического переворачивания карт. Если вы не знакомы с тем, какUIView
анимацииUIView
, это может выглядеть немного странно. Несмотря на то, что мы добавляли анимацию каждой карты последовательно в цикле, анимации объединяются в одну транзакцию анимации и выполняются одновременно, то есть карты объединяются.
Пересмотрите игровую площадку и замените любой текст в редакторе следующим:
1
2
3
4
5
|
import XCPlayground
import UIKit
let gc = GameController()
XCPlaygroundPage.currentPage.liveView = gc
|
Убедитесь, что график виден. Вид контроллера представления должен ожить и показать нам сетку карточек 4х4 с милыми мультяшными животными, которые переворачиваются, чтобы показать нам заднюю часть карточек. Прямо сейчас, мы не можем сделать много с этим представлением, потому что мы еще не запрограммировали никакого взаимодействия с ним. Но это определенно начало.
4. Изменение переменных в игровой площадке
Давайте теперь изменим задние грани карт с сплошного красного на изображение, в частности, b.png в папке Resources . Добавьте следующую строку в нижней части игровой площадки.
1
|
gc.backImage = UIImage(named: «b»)!
|
Через секунду или две вы увидите, что оборотные стороны карточек изменились с простого красного на мультипликационную руку.
Давайте теперь попробуем изменить свойство padding
, которому мы присвоили значение по умолчанию 20 в Game.swift . Пространство между картами должно увеличиться. Добавьте следующую строку в нижней части игровой площадки:
1
|
gc.padding = 75
|
Дождитесь обновления живого изображения и увидите, что … ничего не изменилось.
5. Краткий обход
Чтобы понять, что происходит, вы должны иметь в виду, что такие объекты, как контроллеры представлений и связанные с ними представления, имеют сложный жизненный цикл. Мы собираемся сосредоточиться на последнем, то есть взглядах. Создание и обновление представления контроллера представления является многоступенчатым процессом. В определенные моменты жизненного цикла представления уведомления отправляются в UIViewController
, информируя его о том, что происходит. Что еще более важно, программист может подключиться к этим уведомлениям, вставив код для направления и настройки этого процесса.
loadView()
и viewDidAppear(_:)
— это два метода, которые мы использовали для подключения к жизненному циклу представления. Эта тема несколько затрагивает и выходит за рамки этого обсуждения, но для нас важно то, что код на игровой площадке после назначения контроллера представления в качестве liveView
игровой площадки выполняется некоторое время между вызовом viewWillAppear(_:)
и вызов viewDidAppear(_:)
. Вы можете проверить это, изменив какое-либо свойство на площадке и добавив операторы печати к этим двум методам, чтобы отобразить значение этого свойства.
Проблема со значением padding
не имеющего ожидаемого визуального эффекта, состоит в том, что к тому времени представление и его подпредставления уже были выложены. Имейте в виду, что всякий раз, когда вы вносите изменения в код, игровая площадка запускается заново с самого начала. В этом смысле эта проблема не относится к игровым площадкам. Даже если вы разрабатывали код для запуска на симуляторе или на физическом устройстве, часто вам нужно было бы написать дополнительный код, чтобы гарантировать, что изменение значения свойства окажет желаемый эффект на внешний вид или содержимое представления.
Вы можете спросить, почему мы смогли изменить значение свойства backImage
и увидеть результат, не делая ничего особенного. Обратите внимание, что свойство backImage
фактически впервые используется в viewDidAppear(_:)
, и к этому времени оно уже подобрало свое новое значение.
6. Наблюдение за свойствами и принятие мер
Наш способ справиться с этой ситуацией будет заключаться в отслеживании изменений в значении padding
и изменении размера / изменения положения представления и подпредставлений. К счастью, это легко сделать с помощью удобной функции наблюдения свойств Swift. Начните с раскомментирования кода для resetGrid()
в Game.swift :
01
02
03
04
05
06
07
08
09
10
|
// (7): reset grid
func resetGrid() {
view.frame = CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight)
for v in view.subviews {
if let card = v as?
card.center = centerOfCardAt(card.x, card.y)
}
}
}
|
Этот метод пересчитывает положение кадра представления и каждого объекта Card
на основе новых значений viewWidth
и viewHeight
. Напомним, что эти свойства вычисляются на основе значения padding
, которое только что было изменено.
Также измените код для padding
чтобы использовать наблюдатель didSet
, тело которого, как следует из названия, выполняется всякий раз, когда мы устанавливаем значение padding
:
1
2
3
4
5
6
|
// (1): public variables so we can manipulate them in the playground
public var padding = CGFloat(20) {
didSet {
resetGrid()
}
}
|
resetGrid()
метод resetGrid()
и представление обновляется, чтобы отразить новый интервал. Вы можете проверить это на детской площадке.
Похоже, мы смогли довольно легко все исправить. В действительности, когда я впервые решил, что хочу иметь возможность взаимодействовать со свойством padding
, мне пришлось вернуться назад и внести изменения в код в Game.swift . Например, мне пришлось абстрагировать вычисление центра Card
в отдельной функции ( centerOfCardAt(_:_:)
), чтобы аккуратно и независимо (пере) вычислять позиции карт в centerOfCardAt(_:_:)
время, когда их нужно было разложить.
Создание вычисленных свойств для viewWidth
и viewHeight
также помогло. Хотя этот вид переписывания — это то, к чему вы должны быть готовы в качестве компромисса с отсутствием особой предварительной разработки, его можно уменьшить с некоторой предусмотрительностью и опытом.
7. Игровая логика и сенсорное взаимодействие
Теперь пришло время реализовать логику игры и позволить себе взаимодействовать с ней посредством прикосновения. Начните с раскомментирования firstCard
свойства GameController
классе GameController
:
1
|
var firstCard: Card?
|
Напомним, что логика игры предполагает выявление двух карт, одна за другой. Эта переменная отслеживает, является ли бросок карты, выполненный игроком, первым из двух или нет.
Добавьте следующий метод в GameController
класса GameController
перед завершающей фигурной скобкой:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
func handleTap(gr: UITapGestureRecognizer) {
let v = view.hitTest(gr.locationInView(view), withEvent: nil)!
if let card = v as?
UIView.transitionWithView(
card, duration: 0.5,
options: .TransitionFlipFromLeft,
animations: {card.image = UIImage(named: String(card.tag))}) { // trailing completion handler:
_ in
card.userInteractionEnabled = false
if let pCard = self.firstCard {
if pCard.tag == card.tag {
UIView.animateWithDuration(
0.5,
animations: {card.alpha = 0.0},
completion: {_ in card.removeFromSuperview()})
UIView.animateWithDuration(
0.5,
animations: {pCard.alpha = 0.0},
completion: {_ in pCard.removeFromSuperview()})
} else {
UIView.transitionWithView(
card,
duration: 0.5,
options: .TransitionFlipFromLeft,
animations: {card.image = self.backImage})
{ _ in card.userInteractionEnabled = true }
UIView.transitionWithView(
pCard,
duration: 0.5,
options: .TransitionFlipFromLeft,
animations: {pCard.image = self.backImage})
{ _ in pCard.userInteractionEnabled = true }
}
self.firstCard = nil
} else {
self.firstCard = card
}
}
}
}
|
Это длительный метод. Это связано с тем, что он объединяет все необходимые сенсорные функции, игровую логику, а также связанные анимации в одном методе. Давайте посмотрим, как этот метод работает:
- Во-первых, есть проверка, чтобы убедиться, что пользователь действительно коснулся экземпляра
Card
. Это так же,as?
построить, что мы использовали ранее. - Если пользователь коснулся экземпляра
Card
, мы переворачиваем его, используя анимацию, аналогичную той, которую мы реализовали ранее. Единственный новый аспект заключается в том, что мы используем обработчик завершения, который выполняется после завершения анимации, чтобы временно отключить сенсорные взаимодействия для этой конкретной карты, установив свойствоuserInteractionEnabled
этой карты. Это препятствует тому, чтобы игрок перевернул ту же самую карту. Обратите внимание на_ in
конструкции, которая используется несколько раз в этом методе. Это просто говорит о том, что мы хотим игнорировать параметрBool
который принимает обработчик завершения. - Мы выполняем код в зависимости от того, было ли
firstCard
присвоено ненулевое значение с использованием необязательного связывания, знакомого Swift,if let
конструироватьif let
. - Если
firstCard
не ноль, то это была вторая карта последовательности, которую игрок перевернул. Теперь нам нужно сравнить лицо этой карты с предыдущей (путем сравнения значенийtag
), чтобы увидеть, получили ли мы совпадение или нет. Если мы это сделали, мы анимируем исчезающие карты (устанавливая ихalpha
в 0). Мы также удаляем эти карты из поля зрения. Если теги не равны, то есть карты не совпадают, мы просто переворачиваем их лицевой стороной вниз и устанавливаем дляuserInteractionEnabled
значениеtrue
чтобы пользователь мог выбрать их снова. - Исходя из текущего значения
firstCard
, мы устанавливаем либоnil
либо текущую карту. Вот как мы переключаем поведение кода между двумя последовательными касаниями.
Наконец, раскомментируйте следующие два утверждения в инициализаторе GameController
, который добавляет в представление распознаватель жестов касания. Когда распознаватель жестов касания обнаруживает касание, handleTap()
метод handleTap()
:
1
2
|
let tap = UITapGestureRecognizer(target: self, action: #selector(GameController.handleTap(_:)))
view.addGestureRecognizer(tap)
|
Вернитесь к графику игровой площадки и играйте в игру памяти. Не стесняйтесь уменьшить большой padding
мы назначили немного раньше.
Код в handleTap(_:)
является в значительной степени неукрашенной версией того, что я написал в первый раз. Кто-то может возразить, что как один метод он делает слишком много. Или то, что код недостаточно объектно-ориентирован, и что логику и анимацию переворачивания карты следует аккуратно абстрагировать в методы класса Card
. Хотя эти возражения сами по себе не являются недействительными, помните, что быстрое создание прототипов является целью этого учебника, и, поскольку мы не предвидели никакой необходимости взаимодействовать с этой частью кода на игровой площадке, мы могли бы позволить себе быть немного более «взломанным». -ish».
Как только у нас что-то получится и мы решим, что хотим продолжить эту идею, нам, безусловно, придется подумать о рефакторинге кода. Другими словами, сначала заставьте это работать, затем сделайте это быстрым / изящным / симпатичным / …
8. Сенсорная обработка на детской площадке
Несмотря на то, что основная часть учебного пособия уже закончена, но, как интересно, я хочу показать вам, как мы можем писать код обработки касаний прямо на игровой площадке. Сначала мы добавим метод в класс GameController
который позволит нам GameController
на грани карт. Добавьте следующий код в класс GameController
сразу после handleTap(_:)
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public func quickPeek() {
for v in view.subviews {
if let card = v as?
card.userInteractionEnabled = false
UIView.transitionWithView(card, duration: 1.0, options: .TransitionFlipFromLeft, animations: {card.image = UIImage(named: String(card.tag))}) {
_ in
UIView.transitionWithView(card, duration: 1.0, options: .TransitionFlipFromLeft, animations: {card.image = self.backImage}) {
_ in
card.userInteractionEnabled = true
}
}
}
}
}
|
Предположим, нам нужна возможность активировать или деактивировать эту функцию «быстрого просмотра» с игровой площадки. Один из способов сделать это — создать публичное свойство Bool
в классе GameController
которое мы можем установить на игровой площадке. И, конечно, нам нужно было бы написать обработчик жестов в классе GameController
, активируемый другим жестом, который бы quickPeek()
.
Другой способ — написать код обработки жестов прямо на игровой площадке. Преимущество такого способа состоит в том, что мы можем добавить некоторый пользовательский код в дополнение к вызову quickPeek()
. Это то, что мы будем делать дальше. Добавьте следующий код внизу игровой площадки:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
class LPGR {
static var counter = 0
@objc static func longPressed(lp: UILongPressGestureRecognizer) {
if lp.state == .Began {
gc.quickPeek()
counter += 1
print(«You peeked \(counter) time(s).»)
}
}
}
let longPress = UILongPressGestureRecognizer(target: LPGR.self, action: #selector(LPGR.longPressed))
longPress.minimumPressDuration = 2.0
gc.view.addGestureRecognizer(longPress)
|
Чтобы активировать функцию быстрого просмотра, мы будем использовать жест длинного нажатия, то есть игрок удерживает палец на экране в течение определенного времени. Мы используем две секунды в качестве порога.
Для обработки жеста мы создаем класс LPGR
(сокращенный распознаватель жестов длительного нажатия) со свойством static
переменной counter
, чтобы отслеживать, сколько раз мы просмотрели, и static
методом longPressed(_:)
для обработки жеста. ,
Используя static
квалификатор, мы можем избежать создания экземпляра LPGR
поскольку объявленные статические объекты связаны с типом (классом) LPGR
а не с конкретным экземпляром.
Кроме того, в этом подходе нет особых преимуществ. По сложным причинам нам нужно пометить метод как @objc
чтобы компилятор был доволен. Обратите внимание на использование LPGR.self
для ссылки на тип объекта. Также обратите внимание, что в обработчике жестов мы проверяем, является ли state
жеста .Began
. Это связано с тем, что длительный жест нажатия является непрерывным, то есть обработчик будет выполняться многократно, пока пользователь удерживает палец на экране. Поскольку мы хотим, чтобы код выполнялся только один раз при нажатии пальца, мы делаем это, когда жест впервые распознается.
Инкрементный счетчик — это введенный нами пользовательский код, который не зависит от функциональности, предоставляемой классом GameController
. Вы можете просмотреть выходные данные функции print(_:)
(после нескольких просмотров) в консоли внизу.
Вывод
Надеемся, что это руководство продемонстрировало интересный пример быстрого интерактивного прототипирования на игровых площадках Xcode. Помимо причин использования игровых площадок, о которых я упоминал ранее, вы можете придумать и другие сценарии, в которых они могут быть полезны. Например:
- демонстрация прототипа функциональности клиентам и предоставление им возможности выбирать опции и вносить изменения с обратной связью в реальном времени и без необходимости углубляться в мельчайшие детали кода.
- разработка симуляций, например, для физики, где учащиеся могут поиграть с некоторыми значениями параметров и понаблюдать за влиянием симуляции. Фактически, Apple выпустила впечатляющую игровую площадку, которая демонстрирует их интерактивность и объединение физики с помощью
UIDynamics
API. Я призываю вас проверить это .
При использовании игровых площадок для демонстрационных / обучающих целей, таких как эти, вы, вероятно, захотите широко использовать возможности разметки игровых площадок для форматированного текста и навигации.
Команда Xcode, похоже, стремится улучшить игровые площадки по мере выпуска новых версий IDE. Главной новостью является то, что в настоящее время в бета-версии Xcode 8 появятся игровые площадки для iPad . Но, очевидно, игровые площадки не предназначены для замены полноценной Xcode IDE и необходимости тестирования на реальных устройствах при разработке полноценного, функционального приложения. В конечном счете, это всего лишь инструмент, который нужно использовать, когда это имеет смысл, но очень полезный.