0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Сегодня я расскажу и докажу вам, что показать новый экран так, как вы этого хотите, на самом деле не так уж и сложно. Разберём простой пример, где новый экран появляется постепенно, примерно, как проявляется фотография на полароиде. Сначала рассмотрим теоретический аспект этой проблемы, затем перейдём к практике.</p>
1
<p>Сегодня я расскажу и докажу вам, что показать новый экран так, как вы этого хотите, на самом деле не так уж и сложно. Разберём простой пример, где новый экран появляется постепенно, примерно, как проявляется фотография на полароиде. Сначала рассмотрим теоретический аспект этой проблемы, затем перейдём к практике.</p>
2
<p>В процессе показа экрана есть несколько действующих лиц. Разберём, кто есть кто и за что отвечает: 1)<strong>UIViewController</strong>- главный герой в нашей "постановке", его мы будем показывать и скрывать; 2)<strong>UIViewControllerTransitioningDelegate</strong>- наш помощник, который говорит системе, что мы берём управление над показом или скрытием экрана. Каждый контроллер имеет свойство<strong>transitioningDelegate</strong>, и если оно не установлено или на запрос кастомной анимации этот делегат отвечает nil - в таком случае система использует анимации по умолчанию; 3)<strong>UIViewControllerAnimatedTransitioning</strong>- собственно, здесь представлен "сценарий" нашей презентации, как именно и как долго мы хотим, чтобы наш экран показался; 4)<strong>UIViewControllerContextTransitioning</strong>- перед началом процесса показа экрана iOS создаёт контекст, в рамках которого будет протекать весь процесс. Контекст предоставляет нам доступ к участвующим в показе контроллерам, также при окончании показа мы сообщаем ему об этом; 5)<strong>UIKIt</strong>- фреймворк, который предоставляет нам все необходимые для показа инструменты и вообще делает эту процедуру возможной.</p>
2
<p>В процессе показа экрана есть несколько действующих лиц. Разберём, кто есть кто и за что отвечает: 1)<strong>UIViewController</strong>- главный герой в нашей "постановке", его мы будем показывать и скрывать; 2)<strong>UIViewControllerTransitioningDelegate</strong>- наш помощник, который говорит системе, что мы берём управление над показом или скрытием экрана. Каждый контроллер имеет свойство<strong>transitioningDelegate</strong>, и если оно не установлено или на запрос кастомной анимации этот делегат отвечает nil - в таком случае система использует анимации по умолчанию; 3)<strong>UIViewControllerAnimatedTransitioning</strong>- собственно, здесь представлен "сценарий" нашей презентации, как именно и как долго мы хотим, чтобы наш экран показался; 4)<strong>UIViewControllerContextTransitioning</strong>- перед началом процесса показа экрана iOS создаёт контекст, в рамках которого будет протекать весь процесс. Контекст предоставляет нам доступ к участвующим в показе контроллерам, также при окончании показа мы сообщаем ему об этом; 5)<strong>UIKIt</strong>- фреймворк, который предоставляет нам все необходимые для показа инструменты и вообще делает эту процедуру возможной.</p>
3
<p>За создание элементов 1-3 отвечает разработчик, элементы 4-5 предоставляет сама система.</p>
3
<p>За создание элементов 1-3 отвечает разработчик, элементы 4-5 предоставляет сама система.</p>
4
<p>Если формализовать показ нового экрана в алгоритм, то получится следующее: 1) инициация процесса (нажатие пользователем на кнопку, вызов из кода); 2) UIKit спрашивает у контроллера, который мы хотим показать,<strong>transitioningDelegate</strong>, если такового нет, то используется стандартная анимация; 3) UIKit спрашивает у<strong>transitioningDelegate</strong>объект самой анимации (класс, имплементирующий протокол<strong>UIViewControllerAnimatedTransitioning</strong>) animationController(forPresented:presenting:source:), если мы возвращаем nil, то используется стандартная анимация; 4) UIKit создаёт контекст; 5) UIKit спрашивает длительность у объекта анимации<strong>transitionDuration(using:)</strong>; 6) UIKit запускает сам процесс анимации<strong>animateTransition(using:)</strong>; 7) По окончанию анимации мы вызываем<strong>completeTransition(_:)</strong>у контекста.</p>
4
<p>Если формализовать показ нового экрана в алгоритм, то получится следующее: 1) инициация процесса (нажатие пользователем на кнопку, вызов из кода); 2) UIKit спрашивает у контроллера, который мы хотим показать,<strong>transitioningDelegate</strong>, если такового нет, то используется стандартная анимация; 3) UIKit спрашивает у<strong>transitioningDelegate</strong>объект самой анимации (класс, имплементирующий протокол<strong>UIViewControllerAnimatedTransitioning</strong>) animationController(forPresented:presenting:source:), если мы возвращаем nil, то используется стандартная анимация; 4) UIKit создаёт контекст; 5) UIKit спрашивает длительность у объекта анимации<strong>transitionDuration(using:)</strong>; 6) UIKit запускает сам процесс анимации<strong>animateTransition(using:)</strong>; 7) По окончанию анимации мы вызываем<strong>completeTransition(_:)</strong>у контекста.</p>
5
<h2>Теперь разберём на практике</h2>
5
<h2>Теперь разберём на практике</h2>
6
<p>Перед тем, как приступим, предлагаю скачать<a>стартовый проект</a>. В нём вы найдете два<strong>ViewContoller'а</strong>, на одном есть кнопка, при нажатии на которую у нас показывается второй контроллер с фотографией милой панды. Нужно будет перейти к тэгу step_0 (git checkout step_o).</p>
6
<p>Перед тем, как приступим, предлагаю скачать<a>стартовый проект</a>. В нём вы найдете два<strong>ViewContoller'а</strong>, на одном есть кнопка, при нажатии на которую у нас показывается второй контроллер с фотографией милой панды. Нужно будет перейти к тэгу step_0 (git checkout step_o).</p>
7
<p>Пойдём по вышеуказанным шагам.</p>
7
<p>Пойдём по вышеуказанным шагам.</p>
8
<p><strong>1. Нажатие на кнопку Show Panda.</strong></p>
8
<p><strong>1. Нажатие на кнопку Show Panda.</strong></p>
9
<p>При нажатии на кнопку у нас срабатывает<strong>segue</strong>, поэтому мы можем переопределить метод prepare(for: _, sender: _) и сделать необходимые настройки:</p>
9
<p>При нажатии на кнопку у нас срабатывает<strong>segue</strong>, поэтому мы можем переопределить метод prepare(for: _, sender: _) и сделать необходимые настройки:</p>
10
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { guard segue.identifier == .showPandaSegueIdentifier, let destinationVC = segue.destination as? PandaViewController else { return } }<p><strong>2. Зададим transitioningDelegate.</strong></p>
10
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { guard segue.identifier == .showPandaSegueIdentifier, let destinationVC = segue.destination as? PandaViewController else { return } }<p><strong>2. Зададим transitioningDelegate.</strong></p>
11
<p>Зададим PandaViewController transitioningDelegate, делегатом будет выступать наш<strong>ViewController</strong>. Компилятор будет ругаться - это нормально, на следующем шаге мы устраним эту проблему. Добавим эту строчку в самый конец метода prepare(for: _, sender: _):</p>
11
<p>Зададим PandaViewController transitioningDelegate, делегатом будет выступать наш<strong>ViewController</strong>. Компилятор будет ругаться - это нормально, на следующем шаге мы устраним эту проблему. Добавим эту строчку в самый конец метода prepare(for: _, sender: _):</p>
12
destinationVC.transitioningDelegate = self<p><strong>3. Создаём анимацию.</strong></p>
12
destinationVC.transitioningDelegate = self<p><strong>3. Создаём анимацию.</strong></p>
13
<p>Создадим класс<strong>FadePresentAnimationContoller</strong>и унаследуем его от NSObject, а также укажем протокол UIViewControllerAnimatedTransitioning:</p>
13
<p>Создадим класс<strong>FadePresentAnimationContoller</strong>и унаследуем его от NSObject, а также укажем протокол UIViewControllerAnimatedTransitioning:</p>
14
class FadePresentAnimationContoller: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 1.0 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { } }<p>Сейчас наш класс ничего не делает, оставим пока так.</p>
14
class FadePresentAnimationContoller: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 1.0 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { } }<p>Сейчас наш класс ничего не делает, оставим пока так.</p>
15
<p><strong>4. Создаём контекст.</strong></p>
15
<p><strong>4. Создаём контекст.</strong></p>
16
<p>Мы можем получить доступ к созданному контексту через параметр в функциях нашего класса FadePresentAnimationContoller.</p>
16
<p>Мы можем получить доступ к созданному контексту через параметр в функциях нашего класса FadePresentAnimationContoller.</p>
17
<p><strong>5. Задаём длительность нашей анимации.</strong></p>
17
<p><strong>5. Задаём длительность нашей анимации.</strong></p>
18
<p>Укажем 1 секунду. Можете поэкспериментировать и поставить 5 секунд, можно будет насладиться нашей классной анимацией :)</p>
18
<p>Укажем 1 секунду. Можете поэкспериментировать и поставить 5 секунд, можно будет насладиться нашей классной анимацией :)</p>
19
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 1.0 }<p><strong>6. Запускается процесс анимации.</strong></p>
19
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 1.0 }<p><strong>6. Запускается процесс анимации.</strong></p>
20
<p>Остановимся здесь подробнее. Через контекст сначала получим доступ к контроллеру, который мы хотим показать, и создадим так называемый snapshot экрана, с которого уходим. Snapshot - это просто статичный снимок всего экрана.</p>
20
<p>Остановимся здесь подробнее. Через контекст сначала получим доступ к контроллеру, который мы хотим показать, и создадим так называемый snapshot экрана, с которого уходим. Snapshot - это просто статичный снимок всего экрана.</p>
21
guard let targetVC = transitionContext.viewController(forKey: .to), let snapshot = transitionContext.view(forKey: .from) else { return }<p>Теперь нам нужно подготовиться к презентации. В контексте создаётся<strong>UIView containerView</strong>, в это вью система уже помещает view контроллера, с которого мы уходим. Нам остаётся добавить к нему snapshot и view нового контроллера.</p>
21
guard let targetVC = transitionContext.viewController(forKey: .to), let snapshot = transitionContext.view(forKey: .from) else { return }<p>Теперь нам нужно подготовиться к презентации. В контексте создаётся<strong>UIView containerView</strong>, в это вью система уже помещает view контроллера, с которого мы уходим. Нам остаётся добавить к нему snapshot и view нового контроллера.</p>
22
<p>Также установим alpha проперти у вью в 0.0, чтобы дальше у нас была возможность его анимированно проявить:</p>
22
<p>Также установим alpha проперти у вью в 0.0, чтобы дальше у нас была возможность его анимированно проявить:</p>
23
let containerView = transitionContext.containerView containerView.addSubview(snapshot) containerView.addSubview(targetVC.view) targetVC.view.alpha = 0.0<p>Получаем длительность перехода на новый экран, чтобы подстроить саму анимацию. Выставляем alpha в 1.0 и в completion блоке нам нужно удалить snapshot - из контейнера, он нам больше не нужен.</p>
23
let containerView = transitionContext.containerView containerView.addSubview(snapshot) containerView.addSubview(targetVC.view) targetVC.view.alpha = 0.0<p>Получаем длительность перехода на новый экран, чтобы подстроить саму анимацию. Выставляем alpha в 1.0 и в completion блоке нам нужно удалить snapshot - из контейнера, он нам больше не нужен.</p>
24
let duration = transitionDuration(using: transitionContext) UIView.animate(withDuration: duration, animations: { targetVC.view.alpha = 1.0 }) { _ in snapshot.removeFromSuperview() }<p><strong>7. Завершаем нашу презентацию.</strong></p>
24
let duration = transitionDuration(using: transitionContext) UIView.animate(withDuration: duration, animations: { targetVC.view.alpha = 1.0 }) { _ in snapshot.removeFromSuperview() }<p><strong>7. Завершаем нашу презентацию.</strong></p>
25
<p>Нам нужно оповестить контекст, что мы закончили нашу анимацию. В конец completion-блока добавим следующую строку:<strong>transitionContext.transitionWasCancelled</strong>- она говорит нам, был ли переход отменён пользователем или нет.</p>
25
<p>Нам нужно оповестить контекст, что мы закончили нашу анимацию. В конец completion-блока добавим следующую строку:<strong>transitionContext.transitionWasCancelled</strong>- она говорит нам, был ли переход отменён пользователем или нет.</p>
26
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)<p>В результате получается следующее:</p>
26
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)<p>В результате получается следующее:</p>
27
let duration = transitionDuration(using: transitionContext) UIView.animate(withDuration: duration, animations: { targetVC.view.alpha = 1.0 }) { _ in snapshot.removeFromSuperview() transitionContext.completeTransition(!transitionContext.transitionWasCancelled) }
27
let duration = transitionDuration(using: transitionContext) UIView.animate(withDuration: duration, animations: { targetVC.view.alpha = 1.0 }) { _ in snapshot.removeFromSuperview() transitionContext.completeTransition(!transitionContext.transitionWasCancelled) }