Вы так же попробуете такие классные дополнения как:
- Добавление определения движения
- Установка различий между движениями
- Создание собственного UIGestureRecognizer, так что вы сможете пощекотать обезьянку!
Этот туториал подразумевает что вы уже имеете навыки работы в Storyboard. Если вы не работали в нем, то ознакомьтесь с ним здесь, где мы создаем простое приложение в Xcode.
Поехали!
Открывайте свой Xcode 6 и создавайте новый проект iOS\Application\Single View Application. В поле названия проекта введите имя Monkey Pinch, язык выберите Swift, в качестве device, выберите iPhone. Кликаем на Next, выбераем папку для хранения проекта и нажмаем Create.
Прежде чем продолжить, скачайте рабочие материалы и перетащите все шесть файлов в ваш проект. Поставьте галочку на Destination: Copy items if needed, выберите Create grounps и нажмите Finish.
Далее откройте Main.storyboard. Ваш экран (view controller) по умолчанию квадратный, так что вы можете использовать один storyboard сразу для нескольких устройств. В основном вы будете располагать объекты с помощью ограничений (constraints) и размеров классов. Но, так как наше приложение будет предназначено только для iPhone, то вы можете исключить размеры классов. В Инспекторе файлов(File Inspector, если не доступен, то View Menu > Utilities > Show File Inspector) отожмите галочку Use Size Classes. Выберите Choose class data for: iPhone и нажмите Disable Size Classes.
Сейчас ваш вид приобрел размеры и пропорции iPhone 5.
Перетащите Image View в ваш View Controller. Устновите image как monkey.png и измените размер Image View в соответствии размеру самой картинки, выбрав Editor Menu > Size to Fit Content. Перетащите второй Image View, установите его banana.png и аналогично смените его размер. Любым образом расположите эти два Image View внутри своего View Controller. У вас должно получиться что-то вроде этого:
Вот и вся работа по интерфейсу приложения, теперь, добавьте распознаватель движений и вы сможете двигать эти картинки по экрану!
Обзор UIGestureRecognizer
Прежде чем приступить, мы бы хотели немного рассказать про UIGestureRecognizser и объяснить, почему его так удобно использовать.
Давным давно, когда UIGestureRecognizer еще не было, и если вам хотелось распознать движение как перемещение, то вы должны были записать каждое движение внутри UIView. Вы писали touchesBegan (прикосновение началось), touchesMoves (прикосновение двигается), touchesEnded(прикосновение закончено). Каждый программист писал немного отличный код от других программистов, так что в итоге, получались небольшие баги и несоответствия внутри всего приложения.
В iOS 3.0 Apple предложила решение этой проблемы с помощью классов UIGestureRecognizer! Эти классы имели распознавание движений по умолчанию, например: касание (tap), сужение пальцев(pinches), вращение (rotation), смахивание (swipe), ручка (pan), длительные нажатия (long pressures). При их использовании вы не только, не писали целой тонны кода, но и ваши приложения начинали работать стабильно! Но конечно, вы можете использовать старые уведомления системы о движениях, если ваше приложение этого требует.
Использование UIGestureRecognizer очень простое. Вам нужно выполнить следующие шаги:
- Создайте распознаватель движения. Когда вы создаете распознаватель движения, то вы так же указываете и обратную функцию (callback function), так что он может посылать вам обновления о том, где движение началось и где изменилось, где закончилось.
- Добавьте распознаватель движения на view. Каждое движение связано только с одним (и только одним) view. Когда начинается прикосновение в пределах view, то распознаватель движения ищет движение соответствующее прикосновению(или движению), если соответствие найдено, то он запускает обратную функцию.
Вы можете проделать эти шаги программированием (чем мы собственно и займемся чуть позднее в этом туториале), но еще проще добавить распознаватель движения визуально, через Storyboard. Что ж, давайте добавим наше первое движение в проект!
UIPanGestureRecognizer
Посмотрите в Object Library на странице Main.storyboard. Найдите Pan Gesture Recognizer и перетащите его на картинку обезьянки, на Image View. Это создаст распознаватель движения "ручка" и свяжет это движение с картинкой обезьянки. Вы можете проверить связь опознавателя(распознавателя?) с картинкой, если выберите картинку и пройдете в Connections Inspector (View Menu > Utilities > Show Connections Inspector), и убедитесь в том, что Pan Gesture Recognizer находится в опознавателях(распознавателях?) Outlet Collection.
Возможно, у вас есть вопрос, почему мы ассоциируем распознаватель с Image View, а не с самой картиной View? На самом деле этот вариант тоже будет работать нормально, но это менее подходит для нашего проекта. Как только вы связали распознаватель с обезьянкой, запомните, что теперь любые движения внутри нее будут распознаваться. Недостаток этого метода в том, что иногда вы ходите иметь возможность выходить за пределы рамок. В этом случае вы должны добавить распознаватель движения на саму картинку, но вам потребуется написать код, который будет проверять, трогает ли пользователь картинку банана или обезьяны, и соответствующе реагировать.
Сейчас вы создали распознаватель движение и связали его с image view, теперь вам нужно написать обратную функцию, которая будет исполняться при распознавании движения.
Откройте ViewController.swift и добавьте в самый конец класса ViewController следующую функцию:
@IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translationInView(self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPointZero, inView: self.view)
}
UIPanGestureRecognizer вызывает эту функцию, когда распознается движение "ручка». UIPanGestureRecognizer будет последовательно продолжать вызывать эту функцию, пока пользователь ее не прекратит.
UIPanGestureRecognizer передает себя как аргумент этой функции. Вы можете получить величину передвижения пальца пользователя, вызвав функцию «transitionInView:». Учитывайте, что дальность перемещения центра картинки, равна дальности перемещения пальца пользователя.
Это мегаважно, чтобы выставить перемещение на 0, после того как движение закончилось. В противном случае ваши перемещения, будут накладываться и вы увидите, как ваша обезьянка быстро выскочит за пределы экрана!
Хорошо, функция готова, теперь надо связать ее с UIPanGestureRecognizer. В Main.storyboard, переместите ее с зажатым CTRL от Pan Gesture Recognizer на ваш флажокView Controller.
И вот еще что. Если вы запустите приложение и попробуете перетащить обезьянку, то это не сработает. Причиной этого является отключенная по умолчанию поддержка прикосновений у видов, например, image view. Для того, чтобы это исправить, выделите оба вида, откройте Attribute Inspector и поставьте галочку в чекбоксе User Iteraction Enabled.
Запустите заново и проверьте еще раз. Теперь вы можете таскать обезьянку по экрану!
Обратите внимание, что вы не можете перетаскивать банан. Это потому, что каждый распознаватель жеста должен быть привязан только к одному view. Давайте продолжим и добавим другой распознаватель движения для банана, сделав несколько простых шагов:
- Перетащите Pan Gesture Recognizer поверх банановой Image View.
- Перетащите с зажатым CTRL новый Pan Gesture Recognizer на ваш View Controller и соедините его с «handlePan:» функцией.
Попробуйте запустить ваш проект и вы увидите, что теперь и обезьянка, и банан, перетаскиваются по экрану! Достаточно просто реализовать такой классный и веселый эффект, не так ли?
Постепенное торможение
Во многих приложениях Apple вы можете заметить, что после того как вы оторвали палец от экрана, элемент продолжает движение, после чего плавно останавливается. Или вспомните о том, как прокручиваются страницы сайтов.
На самом деле достаточно много способов реализовать такое движение, но мы воспользуемся одной очень простой реализацией для грубого, но красивого эффекта. Наша цель - определить, где заканчивается движение и как быстро двигался палец по экрану, а также анимировать движущийся объект до его конечной точки, основываясь на скорости движения.
- Определить, когда прикосновение закончилось: Обратная функция передается распознавателю движения, потенциально вызванному много раз: когда распознаватель движения меняет свое состояние на начало движения. Вы можете выяснить в каком состоянии находится распознаватель, просто обратившись к его свойству state.
- Определить скорость движения: Некоторые распознаватели движения возвращают дополнительную информацию (взгляните в API). Но есть удобная функция velocityInView, которую вы можете использовать в UIPanGestureRecognizer.
Давайте добавим в конец функцию «handlePan:», которая находится в ViewController.swift:
if recognizer.state == UIGestureRecognizerState.Ended {
// 1
let velocity = recognizer.velocityInView(self.view)
let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
let slideMultiplier = magnitude / 200
println("magnitude: \(magnitude), slideMultiplier: \(slideMultiplier)")
// 2
let slideFactor = 0.1 * slideMultiplier //Increase for more of a slide
// 3
var finalPoint = CGPoint(x:recognizer.view!.center.x + (velocity.x * slideFactor),
y:recognizer.view!.center.y + (velocity.y * slideFactor))
// 4
finalPoint.x = min(max(finalPoint.x, 0), self.view.bounds.size.width)
finalPoint.y = min(max(finalPoint.y, 0), self.view.bounds.size.height)
// 5
UIView.animateWithDuration(Double(slideFactor * 2),
delay: 0,
// 6
options: UIViewAnimationOptions.CurveEaseOut,
animations: {recognizer.view!.center = finalPoint },
completion: nil)
}
Это очень простая функция, которую мы написали для демонстрации инерционного торможения. Она включает в себя следующие шаги:
- Выясним длину вектора скорости или просто его величину(magnitude)
- Если его длина меньше <200, тогда
- Высчитываем конечную точку, основываясь на скорости и slideFactor.
- Проверяем, что конечная точка находится в пределах экрана.
- Анимация view до места остановки.
- Используйте опцию анимации "ease out" для замедления движения по времени.
Скомпилируйте код и попробуйте запустить. Вы должны будете увидеть простое, но красивое замедление движения. Не бойтесь поиграть с кодом, попробуйте улучшить его.
Движение зажимания (щипания) и движение вращения
Ваше приложение здорово прогрессирует, но будет даже еще круче, если мы сможем масштабировать и вращать картинки, используя соответствующие жесты!
Сначала, давайте добавим вот этот код для обратных функций - добавьте следующие функции в ViewController.swift, в конец класса ViewController:
@IBAction func handlePinch(recognizer : UIPinchGestureRecognizer) {
if let view = recognizer.view {
view.transform = CGAffineTransformScale(view.transform,
recognizer.scale, recognizer.scale)
recognizer.scale = 1
}
}
@IBAction func handleRotate(recognizer : UIRotationGestureRecognizer) {
if let view = recognizer.view {
view.transform = CGAffineTransformRotate(view.transform, recognizer.rotation)
recognizer.rotation = 0
}
}
Точно так же как мы работали через UIPanGestureRecognizer, вы можете сделать масштабирование и вращение через UIPinchGestureRecognizer, и UIRotationGestureRecognizer.
Каждый элемент имеет трансформации, которые применяются к нему и о которых вы можете думать как о возможностях: вращения, масштабирования и перевода. Apple имеет много разных встроенных функций, которые упрощают работу с транформациями, такие как CGAffineTransformScale (масштабировать на полученные преобразования) и CGAffineTransformRotate (вращать на полученные преобразования). Здесь мы будем использовать это для обновления преобразования элемента, основываясь на жесте или движении.
Снова, как только вы обновляете элемент, сразу обновляется и движение. Это очень важно, сбрасывать значения масштаба и вращения на значение по умолчанию, так как это поможет вам не допустить путаницы.
Хорошо, теперь давайте привяжем это в Storyboard. Откройте Main.storyboard и проделайте следующие шаги:
- Перетащите Pinch Gesture Recognizer и Rotation Gesture Recognizer на обезьянку, и повторите то же самое с бананом.
- Также, как вы делали ранее, соедините Pinch Gesture Recognizer с функцией View Controller'а «handlePinch:»
- Соедините Rotation Gesture распознаватели с функцией View Controller'а «handleRotate:».
Постройте и запустите приложение. Если возможно, то запустите приложение на каком-то устройстве, так как жесты связанные с двумя и более пальцев, будет сложно изобразить одним курсором. Если вы все-таки решили использовать симулятор, то выполните следующие действия: удерживайте клавишу alt для перетаскивания (это симулирует два пальца), удерживайте одновременно shift и alt, чтобы передвинуть смоделированные пальцы в другое место. Теперь, вы можете вращать и масштабировать обезьяну и банан!
Синхронное распознавание движения
Вы может быть заметили, что если поставить два пальца на обезьянку и один палец на банан, то появляется возможность перетаскивать их одновременно. Совсем неплохо, как думаете?
Однако, обратите внимание, что если вы попытаетесь перетащить обезьянку, и на середине пути решите добавить второй палец, чтобы изменить масштаб, то это работать не будет. По умолчанию, только первый распознаватель объявляет права на движение, и ни один другой распознаватель не может их распознать.
Однако, вы можете переписать это, просто изменив функцию делегата UIGestureRecognizer.
Откройте ViewController.swift и отметьте класс, который реализует UIGestureRecognizerDelegate, как показано ниже:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
Затем реализуйте одну из опциональных функций делегата:
func gestureRecognizer(UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
return true
}
Эта функция спрашивает у распознователя, нормально ли будет распознать еще один, если первый уже найден. По умолчанию, всегда стоит false, но тут вы меняете его на true.
Следующий шаг - откройте Main.storyboard и для каждого распознавателя соедините delegate outlet с view controller.
Постройте и запустите приложение снова. Сейчас, у вас должно получиться выполнять несколько действий одновременно: перетаскивать обезьянку, менять ее масштаб, крутить и продолжать тащить, сразу после этого. Согласитесь, это делает опыт пользователя более красочным.
Программные UIGestureRecognizers
Итак, вы сделали распознаватели движений с помощью редактора Storyboard, но что, если вы очень хотите сделать это программным способом?
Не бойтесь, в этом нет ничего сложного. Так что давайте выясним как это делать, и начнем мы с добавления звука при распознавании жеста нажатия на обезьянку, или на банан.
Чтобы была возможность проиграть звук, вам нужно получить доступ к фреймворку AVFoundation. Наверху файла ViewController.swift добавьте:
import AVFoundation
Добавьте еще следующие изменения в ViewController.swift прямо перед viewDidLoad:
var chompPlayer:AVAudioPlayer? = nil
func loadSound(filename:NSString) -> AVAudioPlayer {
let url = NSBundle.mainBundle().URLForResource(filename as String, withExtension: "caf")
var error:NSError? = nil
let player = AVAudioPlayer(contentsOfURL: url, error: &error)
if error != nil {
println("Error loading \(url): \(error?.localizedDescription)")
} else {
player.prepareToPlay()
}
return player
}
Замените ViewDidLoad следующим кодом:
override func viewDidLoad() {
super.viewDidLoad()
//1
let filteredSubviews = self.view.subviews.filter({
$0.isKindOfClass(UIImageView) })
// 2
for view in filteredSubviews {
// 3
let recognizer = UITapGestureRecognizer(target: self, action:Selector("handleTap:"))
// 4
recognizer.delegate = self
view.addGestureRecognizer(recognizer)
//TODO: Add a custom gesture recognizer too
}
self.chompPlayer = self.loadSound("chomp")
}
Добавьте вниз класса ViewController:
func handleTap(recognizer: UITapGestureRecognizer) {
self.chompPlayer?.play()
}
Код для проигрывания аудио выходит за рамки данного туториала, и мы не будем его тут обсуждать (хотя он до неприличия простой)
Важная часть в viewDidLoad:
- Создайте массив только из обезьянки и банана.
- Цикл через этот массив.
- Создайте UITapGestureRecognizer для каждой image view, указывая на обратную функцию.
- Установите делегат распознавателя программно, и добавьте распознаватель на image view.
Вот и все! Запустите приложение и вы увидите, что вы можете нажимать на изображения для звукового эффекта!
Зависимости UIGestureReognizer
Все работает достаточно хорошо за исключением маленького нюанса. Если вы перетащите объект на маленькое расстояние, то сработает звуковой эффект, а значит, такое движение определится как одиночное нажатие пальцем на экран. Но что действительно нужно, так это чтобы проигрывался звук, только при одиночном нажатии пальца.
Для того, чтобы разобраться с этим, вы можете убрать или изменить обратную функцию делегата для различного поведения, в случае совпадения прикосновения и жеста щипка, но есть еще кое-что, что вы можете сделать с распознавателем движения - это установка различий.
Есть функция «requireGestureRecognizerToFail:», которую вы можете привязать к распознавателю движения. Угадайте, что делает эта функция? ;]
Откройте Main.storyboard, откройте Assistant Editor и убедитесь в том, что ViewController.swift в нем отображается. Перетащите с зажатым ctrl, от распознавателя движения обезьяны, в место под определением класса и соедините его с outlet mobkeryPan. Повторите то же самое для банана, но только соедините его с bananaPan.
После чего, просто добавьте две строки в ViewDidLoad, сразу после TODO.
recognizer.requireGestureRecognizerToFail(monkeyPan)
recognizer.requireGestureRecognizerToFail(bananaPan)
Теперь, распознаватель одиночного прикосновения будет запускаться только тогда, когда не будет обнаружено движения перетаскивания. Здорово, не так ли? Вы можете использовать такую технику и в других проектах.
Пользовательский UIGestureRecognizer
На этом этапе вы уже знаете все, что нужно знать для использования встроенного распознавателя движений в ваших приложениях. Но что, если вы хотите распознать такое движение, которого нет во встроенных распознавателях движений?
Что ж, вы всегда можете написать что-то свое! Сейчас, вы попробуете написать очень простой распознаватель для определения того, что вы пытаетесь "пощекотать" обезьянку или банан, проведя несколько раз слева направо.
Создайте новый файл iOS\Source\Swift File. Назовите файл TickleGestureRecognizer.
Замените содержимое файла TickleGestureRecognizer.swift следующим содержимым:
import UIKit
class TickleGestureRecognizer:UIGestureRecognizer {
// 1
let requiredTickles = 2
let distanceForTickleGesture:CGFloat = 25.0
// 2
enum Direction:Int {
case DirectionUnknown = 0
case DirectionLeft
case DirectionRight
}
// 3
var tickleCount:Int = 0
var curTickleStart:CGPoint = CGPointZero
var lastDirection:Direction = .DirectionUnknown
Вот, что вы сделали только что, разбираем последовательно:
- Тут прописаны константы, которые определяют какое движение вам будет нужно. Обратите внимание, что тип requiredTickles будет выведен как Int, но тип distanceForTickleGesture будет выведен как Double, а нам нужен CGFloat, так как у нас, в противном случае, возникнут сложности при вычислении CGPoints.
- Возможные варианты направления щекотки.
- Здесь мы используем три переменные для отслеживания распознавания этого движения:
- tickleCount:Сколько раз пользователь сменил направление движения его пальца (двигаясь на минимальное количество точек). Если пользователь сменил направление движения три раза, то такое движение уже распознается как щекотка.
- curTickleStart:Точка, в которой пользователь начал движение щекотки. Вы обновляете эту точку каждый раз, когда пользователь меняет направление(двигаясь на минимальное количество точек).
- lastDirection:Последнее направление, в котором двигался палец. Изначально оно неизвестно, но после того, как пользователь передвинул палец на минимальное расстояние, вы проверяете, двигался ли он вправо или влево и, соответственно, обновляете lastDirection.
Конечно, эти свойства специфичны для каждого движения, которое вы определяете. У вас будут свои собственные свойства движения, в зависимости от самого движения, но в целом, идея вам ясна.
Еще одно, что вы должны обновлять - это состояние, когда щекотка завершилась. Вы меняете состояние на то, что движение завершено. Изначально в Objective-C UIGestureRecognizer свойство state - это свойство только для чтения, так что вам нужно создать Bridging Header (соединяющий заголовок) для переопределения этого свойства.
Самый простой способ сделать это - создать класс Objective-C, после чего удалить часть реализации.
Создайте новый файл iOS\Source\Objective-C файл. Вызовите файл Bridging-Header и нажмите Create. После чего у вас спросят, хотите ли вы настроить соединяющий заголовок. Выберите Yes. Два новых файла будут добавлены в ваш проект:
- MonkeyPinch-Bridging-Header.h
- Bridging-Header.m
Удалите файл Bridging-Header.m
Добавьте этот Objective-C код в MonkeyPinch-Bridging-Header.h:
#import
Сейчас у вас должна быть возможность изменить свойство state у UIGestureRecognizer в TickleGestureRecognizer.swift.
Переключитесь на TickleGestureRecognizer.swift и добавьте следующие функции в класс:
override func touchesBegan(touches: Set!, withEvent event: UIEvent!) {
if let touch = touches.first as? UITouch {
self.curTickleStart = touch.locationInView(self.view)
}
}
override func touchesMoved(touches: Set!, withEvent event: UIEvent!) {
if let touch = touches.first as? UITouch {
let ticklePoint = touch.locationInView(self.view)
let moveAmt = ticklePoint.x - curTickleStart.x
var curDirection:Direction
if moveAmt < 0 {
curDirection = .DirectionLeft
} else {
curDirection = .DirectionRight
}
//moveAmt is a Float, so self.distanceForTickleGesture needs to be a Float also
if abs(moveAmt) < self.distanceForTickleGesture {
return
}
if self.lastDirection == .DirectionUnknown ||
(self.lastDirection == .DirectionLeft && curDirection == .DirectionRight) ||
(self.lastDirection == .DirectionRight && curDirection == .DirectionLeft) {
self.tickleCount++
self.curTickleStart = ticklePoint
self.lastDirection = curDirection
if self.state == .Possible && self.tickleCount > self.requiredTickles {
self.state = .Ended
}
}
}
}
override func reset() {
self.tickleCount = 0
self.curTickleStart = CGPointZero
self.lastDirection = .DirectionUnknown
if self.state == .Possible {
self.state = .Failed
}
}
override func touchesEnded(touches: Set!, withEvent event: UIEvent!) {
self.reset()
}
override func touchesCancelled(touches: Set!, withEvent event: UIEvent!) {
self.reset()
}
Здесь получилось достаточно много кода, но мы не будем рассказывать о его специфике, так как это не особенно важно. Важно то, как это работает: вы переписываете функции touchesBegan и touchesMoved, touchesEnded и touchesCancelled класса UIGestureRecognizer на пользовательский код, на определения прикосновения, и определения движения.
Как только вы определили движение, вы хотите отправлять обновления возвратной функции. Вы можете сделать это, изменив свойство state распознавателя. Обычно, как только движение начинается, вы меняете состояние на .Began, отправляете обновления .Changed и завершаете .Ended.
Но для этого простого распознавателя движения, как только пользователь пощекотал объект, вы отмечаете, что движение завершено. Обратную функцию вам нужно добавить в ViewController.swift, которая будет вызвана и вы сможете реализовать код там.
Хорошо, давайте используем наш новый распознаватель! Откройте ViewController.swift и сделайте следующие изменения:
Добавьте вверх класса
var hehePlayer:AVAudioPlayer? = nil
В viewDidLoad, сразу после TODO, добавьте:
let recognizer2 = TickleGestureRecognizer(target: self, action: Selector("handleTickle:"))
recognizer2.delegate = self
view.addGestureRecognizer(recognizer2)
В конец viewDidLoad добавьте:
self.hehePlayer = self.loadSound("hehehe1")
Добавьте в начало handlePan:(нужно выключить распознование перетаскивания, чтобы расопзнать щекотку):
//comment for panning
//uncomment for tickling
return;
В конец класса добавьте обратную функцию:
func handleTickle(recognizer:TickleGestureRecognizer) {
self.hehePlayer?.play()
}
Вы можете видеть, что использование пользовательского распознавателя жестов, так же просто как и встроенных!
Скомпилируйте код и запустите "хе-хе, щекотно!"
Здесь вы можете скачать конечный вариант проекта!
Поздравляем - теперь, вы мастер распознавателя движений как встроенных, так и пользовательских! Взаимодействие с прикосновением является очень важной частью устройств iOS, и UIGestureRecognizer является ключом к удобному и простому использованию движений, в качестве управления, что значительно превосходит кнопочное управление.
Что дальше?
Дальше, вы можете продолжить изучать наши туториалы по мере их появления, а также, параллельно читать перевод официальной книги по языку программирования Swift. И, для более подробного изучения языка, вы можете пройти наши курсы!
Урок подготовил: Иван Акулов
Источник урока: http://www.raywenderlich.com/76020/using-uigesturerecognizer-with-swift-tutorial