Статьи

Как создать пользовательские переходы и анимацию контроллера вида

UIKit является очень мощным фреймворком и предоставляет различные способы перехода между контроллерами представления. Некоторые из анимаций, предоставляемых UIKit, включают горизонтальное скольжение (с помощью push-перехода), вертикальное скольжение, перекрестное затухание и скручивание страницы. Иногда, однако, вам нужно иметь собственный переход между контроллерами представления, чтобы создать привлекательный дизайн или предоставить уникальный пользовательский опыт. Хорошим примером пользовательских переходов являются фотографии Apple для iOS при выборе фотографии.

В этом руководстве я покажу вам, как создавать свои собственные переходы с помощью нескольких API UIKit.

Хотя API, используемые для создания пользовательских переходов, были представлены в iOS 7, в этом руководстве используются Auto Layout и Swift 2, что означает, что вам нужно запустить Xcode 7+ в OS X Yosemite или более поздней версии. Вам также необходимо скачать стартовый проект с GitHub .

При реализации пользовательского перехода контроллера представления необходимо помнить о двух основных компонентах:

  • контроллер анимации , также называемый аниматором
  • переходящий делегат , контроллер представления, который вы назначаете

Объект аниматора отвечает за переход с точки зрения как продолжительности, так и фактической логики анимации представлений. Контроллер анимации в вашем приложении может быть объектом любого типа, если он соответствует протоколу UIViewControllerAnimatedTransitioning .

Переходящий делегат отвечает за предоставление контроллера анимации, который будет использоваться для пользовательского перехода. Указанный вами объект делегата должен соответствовать протоколу UIViewControllerTransitioningDelegate .

Откройте стартовый проект и запустите ваше приложение. Когда вы нажимаете кнопку « Нажать для просмотра» , в настоящее время используется стандартный вертикальный модальный переход.

Создайте новый файл, выбрав New> File … в меню File . Из показанных параметров выберите « iOS»> «Источник»> « Файл Swift» и назовите файл « CustomTransition» . Этот файл будет содержать логику, необходимую для пользовательского перехода.

Во-первых, мы собираемся определить класс контроллера анимации, который будет использоваться для пользовательского перехода. Добавьте следующий код в CustomTransition.swift :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import UIKit
 
enum TransitionType {
    case Presenting, Dismissing
}
 
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
    var duration: NSTimeInterval
    var isPresenting: Bool
    var originFrame: CGRect
     
    init(withDuration duration: NSTimeInterval, forTransitionType type: TransitionType, originFrame: CGRect) {
        self.duration = duration
        self.isPresenting = type == .Presenting
        self.originFrame = originFrame
         
        super.init()
    }
     
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return self.duration
    }
}

Мы определяем перечисление TransitionType , которое используется при создании объекта AnimationController .

Далее мы определяем класс AnimationController с несколькими свойствами. Свойство duration будет использоваться для определения продолжительности анимации и является значением, возвращаемым в UIViewControllerAnimatedTransitioning протокола UIViewControllerAnimatedTransitioning transitionDuration(_:) . Эта длительность не обязательно должна быть переменной, но ее легче изменить, если она задается только один раз при создании контроллера анимации. isPresenting и originFrame будут использоваться для создания анимации перехода.

В этот момент Xcode должен представить вам ошибку. Это потому, что мы не реализовали обязательный метод протокола UIViewControllerAnimatedTransitioning . Прежде чем мы реализуем этот метод, необходимо знать одну важную вещь.

Когда начинается пользовательский переход, UIKit предоставляет вам контейнерное представление, в котором вы должны выполнить анимацию для перехода. В этом представлении контейнера вы должны вручную добавить представление контроллера представления, к которому вы переходите. Это контейнерное представление действует только в течение переходного периода и удаляется из иерархии представления, как только анимация завершится.

Теперь мы собираемся реализовать пользовательскую анимацию. Добавьте следующий метод в класс AnimationController :

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
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    let containerView = transitionContext.containerView()!
     
    let fromView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view
    let toView = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!.view
     
    let detailView = self.isPresenting ?
     
    if self.isPresenting {
        containerView.addSubview(toView)
    } else {
        containerView.insertSubview(toView, belowSubview: fromView)
    }
     
    detailView.frame.origin = self.isPresenting ?
    detailView.frame.size.width = self.isPresenting ?
    detailView.layoutIfNeeded()
     
    for view in detailView.subviews {
        if !(view is UIImageView) {
            view.alpha = isPresenting ?
        }
    }
     
    UIView.animateWithDuration(self.duration, animations: { () -> Void in
        detailView.frame = self.isPresenting ?
        detailView.layoutIfNeeded()
         
        for view in detailView.subviews {
            if !(view is UIImageView) {
                view.alpha = self.isPresenting ?
            }
        }
    }) { (completed: Bool) -> Void in
        transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
    }
}

Мы начнем с этого метода, извлекая представление контейнера из предоставленного контекста перехода, используя метод containerView() . Мы обращаемся к представлениям from и to , вызывая метод viewControllerForKey(_:) , передавая UITransitionContextFromViewControllerKey и UITransitionContextToViewControllerKey соответственно.

В iOS 8 и более поздних версиях вы можете получить прямой доступ к представлениям, используя метод viewForKey(_:) и ключи UITransitionContextFromViewKey и UITransitionContextToViewKey .

В теле метода мы анимируем детальное представление для увеличения или уменьшения с использованием существующих API-интерфейсов анимации UIView .

Последнее, что следует отметить, — это метод completeTransition(_:) вызываемый для объекта контекста перехода. Этот метод должен вызываться после завершения анимации, чтобы система знала, что ваши контроллеры представления завершили переход. Этот метод принимает логическое значение в качестве единственного параметра, который указывает, был ли переход выполнен или нет.

С помощью этой реализации мы создали полнофункциональный контроллер анимации. Чтобы реально реализовать это, нам теперь нужно установить переходящий делегат.

Как я упоминал ранее, задача делегата перехода заключается в предоставлении объекта контроллера анимации для перехода между двумя контроллерами представления. Переходящий делегат может быть любым объектом, который вы хотите, но обычная практика — сделать контроллер представления представителем делегатом.

В CustomTransition.swift добавьте следующий код ниже определения класса AnimationController :

01
02
03
04
05
06
07
08
09
10
11
12
13
extension ViewController: UIViewControllerTransitioningDelegate {
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        segue.destinationViewController.transitioningDelegate = self
    }
     
    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
        return AnimationController(withDuration: 3.0, forTransitionType: .Dismissing, originFrame: self.image.frame)
    }
     
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning?
        return AnimationController(withDuration: 3.0, forTransitionType: .Presenting, originFrame: self.image.frame)
    }
}

ViewController это расширение, мы ViewController класс ViewController соответствие с протоколом UIViewControllerTransitioningDelegate и реализуем три метода. Первый, prepareForSegue(_:sender:) , используется для обозначения текущего экземпляра ViewController в качестве объекта transitioningDelegate DetailViewController объекта DetailViewController . Два других метода создают объект AnimationController для представления и отклонения контроллера представления с использованием инициализатора, который мы определили ранее. Если вы nil любым из этих методов, вместо него будет использован переход контроллера представления по умолчанию или стандартный.

После настройки переходного делегата пришло время создать и запустить ваше приложение. На этот раз, когда вы нажмете кнопку « Нажать для просмотра» , вы увидите, как значок Xcode увеличился в размере, а другие метки исчезли. Аналогичным образом, когда вы нажмете кнопку « Нажать, чтобы закрыть» , вы должны увидеть ту же анимацию, но в обратном порядке.

Пользовательский переход

Поздравляю. Вы успешно создали свой самый первый пользовательский переход контроллера представления на iOS.

Чтобы сделать пользовательский переход еще лучше, мы сделаем его интерактивным и отзывчивым. Хорошим примером этого является жест смахивания UINavigationController от левого края, чтобы вернуться назад.

Чтобы сделать пользовательский переход интерактивным, сначала нужно иметь объект, соответствующий протоколу UIViewControllerInteractiveTransitioning . Для этого урока мы будем использовать класс, предоставляемый UIKit, который уже соответствует этому протоколу, UIPercentDrivenInteractiveTransition .

Чтобы легко обмениваться данными между контроллерами представления (делегат перехода и контроллер представления, определяющий процент завершения перехода), откройте DetailViewController.swift и добавьте следующее свойство в класс DetailViewController :

1
var rootViewController: ViewController!

Затем добавьте следующий код в метод didPanDown(_:) класса DetailViewController :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
@IBAction func didPanDown(sender: UIPanGestureRecognizer) {
    let progress = sender.translationInView(self.view).y/self.view.frame.size.height
 
    switch sender.state {
    case .Began:
        self.rootViewController.interactionController = UIPercentDrivenInteractiveTransition()
        self.dismissViewControllerAnimated(true, completion: nil)
    case .Changed:
        self.rootViewController.interactionController?.updateInteractiveTransition(progress)
    case .Ended:
        if progress >= 0.5 {
            self.rootViewController.interactionController?.finishInteractiveTransition()
        } else {
            self.rootViewController.interactionController?.cancelInteractiveTransition()
        }
 
        self.rootViewController.interactionController = nil
    default:
        self.rootViewController.interactionController?.cancelInteractiveTransition()
        self.rootViewController.interactionController = nil
    }
}

В didPanDown(_:) мы вычисляем значение переменной progress с точки зрения того, насколько далеко пользователь панорамировал по отношению к подробному представлению. Если панорамирование только началось, мы создаем объект контроллера взаимодействия и начинаем удаление контроллера представления. Всякий раз, когда жест панорамирования перемещается в представлении, мы обновляем контроллер взаимодействия тем, насколько далеко должен был пройти переход.

Наконец, когда панорамирование закончилось, мы либо завершаем, либо отменяем переход, используя finishInteractiveTransition() и cancelInteractiveTransition() соответственно.

Затем вернитесь к CustomTransition.swift и замените метод prepareForSegue(_:sender:) в ViewController класса ViewController следующим:

1
2
3
4
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    segue.destinationViewController.transitioningDelegate = self
    (segue.destinationViewController as? DetailViewController)?.rootViewController = self
}

В prepareForSegue(_:sender:) мы предоставляем контроллеру подробного представления доступ к корневому контроллеру представления.

Наконец, добавьте следующий метод к расширению ViewController :

1
2
3
func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
    return self.interactionController
}

Метод interactionControllerForDismissal(_:) возвращает rootController контроллера корневого представления. Если это nil (например, если кнопка нажата), вместо этого будет использоваться пользовательская анимация.

Как и следовало ожидать, интерактивный контроллер также можно использовать при представлении контроллеров представления, реализуя метод interactionControllerForPresentation(_:) ControllerPresentation interactionControllerForPresentation(_:) .

Создайте и запустите приложение в последний раз и, представив свой контроллер детального просмотра, перетащите курсор вниз на экран, и вы увидите, что переход перемещается синхронно с положением пальца.

Теперь вам должно быть удобно создавать полностью интерактивные, настраиваемые переходы контроллера представления на iOS. Как видите, эти API сдерживаются только возможностями анимации UIKit и Core Animation. Они могут быть использованы практически для любого вида перехода. Как всегда, оставляйте свои комментарии и отзывы в комментариях ниже.