Як змінити анімацію Push і Pop у навігаційному додатку


221

У мене є програма для навігації, і я хочу змінити анімацію анімації push і pop. Як би я це зробив?

Редагувати 2018 рік

На це питання було багато відповідей, і це вже досить давно, я переобрав відповідь на те, що, на мою думку, зараз є найбільш актуальним. Якщо хтось думає інакше, будь ласка, повідомте мене в коментарях


25
Щодо iOS 7, для цього існує офіційний API; див . спеціальну підтримку анімації переходу UINavigationControllerDelegate . Про це також є відео WWDC 2013 .
Джессі Русак

Я додав відповідь (нижче) для цього в Swift - я наткнувся на це питання, питаючи про реалізацію Swift, тому подумав, що я прислухаюся до своєї подальшої реалізації.
djbp

1
Про дуже хороший підручник з офіційним (iOS 7+) API дивіться: bradbambara.wordpress.com/2014/04/11/…
nikolovski

1
@JesseRusak оновив посилання на WWDC 2013 Відео: developer.apple.com/videos/play/wwdc2013-218
Войцех Рутковський

1
Змінили мою прийняту відповідь хлопці nals. Сподіваюсь, це допомагає! GLHF
Jab

Відповіді:


35

Як змінити анімацію Push і Pop у навігаційному додатку ...

На 2019 рік "остаточна відповідь!"

Преамбула:

Скажімо, ви новачок у розробці iOS. Блукаючи, Apple надає два переходи, якими легко користуватися. Це: "перехресне перетворення" та "перевертання".

Але, звичайно, "перехресне перетворення" і "фліп" марно. Вони ніколи не використовуються. Ніхто не знає, чому Apple забезпечила ці два марні переходи!

Так:

Скажіть, ви хочете зробити звичайний, звичайний перехід, наприклад "слайд". У такому випадку вам доведеться виконати ВЕЛИЧИЙ обсяг роботи! .

Ця робота, пояснюється в цій публікації.

Просто повторити:

Дивно: з прошивкою, якщо ви хочете простий, найбільш поширені, щоденні переходи (наприклад, звичайний слайд), ви повинні всі роботи по реалізації повного користувальницького переходу .

Ось як це зробити ...

1. Вам потрібен звичай UIViewControllerAnimatedTransitioning

  1. Вам потрібен власний бул popStyle. (Це спливає, чи спливає?)

  2. Ви повинні включити transitionDuration(тривіальний) та основний дзвінок,animateTransition

  3. Насправді ви повинні написати дві різні процедури для всередині animateTransition. Один для поштовху, а один для попса. Напевно, назвіть їх animatePushі animatePop. Всередині animateTransitionпросто гілка на popStyleдві процедури

  4. Наведений нижче приклад робить простий перехід / переїзд

  5. У вашому animatePushі animatePopпідпрограми. Ви повинні отримати "з перегляду" та "для перегляду". (Як це зробити, показано в прикладі коду.)

  6. і ви повинні addSubview для нового перегляду "до".

  7. і ви повинні зателефонувати completeTransitionв кінці свого аніме

Так ..

  class SimpleOver: NSObject, UIViewControllerAnimatedTransitioning {
        
        var popStyle: Bool = false
        
        func transitionDuration(
            using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.20
        }
        
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            
            if popStyle {
                
                animatePop(using: transitionContext)
                return
            }
            
            let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
            let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
            
            let f = transitionContext.finalFrame(for: tz)
            
            let fOff = f.offsetBy(dx: f.width, dy: 55)
            tz.view.frame = fOff
            
            transitionContext.containerView.insertSubview(tz.view, aboveSubview: fz.view)
            
            UIView.animate(
                withDuration: transitionDuration(using: transitionContext),
                animations: {
                    tz.view.frame = f
            }, completion: {_ in 
                    transitionContext.completeTransition(true)
            })
        }
        
        func animatePop(using transitionContext: UIViewControllerContextTransitioning) {
            
            let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
            let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
            
            let f = transitionContext.initialFrame(for: fz)
            let fOffPop = f.offsetBy(dx: f.width, dy: 55)
            
            transitionContext.containerView.insertSubview(tz.view, belowSubview: fz.view)
            
            UIView.animate(
                withDuration: transitionDuration(using: transitionContext),
                animations: {
                    fz.view.frame = fOffPop
            }, completion: {_ in 
                    transitionContext.completeTransition(true)
            })
        }
    }

І потім ...

2. Використовуйте його у контролері перегляду.

Примітка: як не дивно, робити це потрібно лише в "першому" контролері перегляду. (Той, що знаходиться "внизу".)

З тією, яку ви спливають зверху , нічого не робіть . Легко.

Тож ваш клас ...

class SomeScreen: UIViewController {
}

стає ...

class FrontScreen: UIViewController,
        UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
    
    let simpleOver = SimpleOver()
    

    override func viewDidLoad() {
        
        super.viewDidLoad()
        navigationController?.delegate = self
    }

    func navigationController(
        _ navigationController: UINavigationController,
        animationControllerFor operation: UINavigationControllerOperation,
        from fromVC: UIViewController,
        to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        simpleOver.popStyle = (operation == .pop)
        return simpleOver
    }
}

Це воно.

Натисніть і попустіть так само, як зазвичай, без змін. Натиснути ...

let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil)
          .instantiateViewController(withIdentifier: "nextScreenStoryboardID")
          as! NextScreen
navigationController?.pushViewController(n, animated: true)

і, щоб відобразити це, ви можете, якщо вам подобається, просто зробити це на наступному екрані:

class NextScreen: TotallyOrdinaryUIViewController {
    
    @IBAction func userClickedBackOrDismissOrSomethingLikeThat() {
        
        navigationController?.popViewController(animated: true)
    }
}

Phew.


3. Також насолоджуйтесь іншими відповідями на цій сторінці, які пояснюють, як перемогти анімоване переклад

Перейдіть до відповіді @AlanZeino та @elias, щоб отримати докладнішу інформацію про те, як це робити AnimatedTransitioningв додатках для iOS сьогодні!


Відмінно! Якщо я хочу, щоб рух навігаційного пальця назад підтримував той самий AnimatedTransitioning теж. Будь-яка ідея?
sam chi wen

дякую @samchiwen - справді саме це animatePushі animatePopє .. два різних напрямки!
Fattie

268

Я зробив наступне, і це працює чудово .. і це просто і легко зрозуміти ..

CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
//transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[[self navigationController] popViewControllerAnimated:NO];

І те саме для штовхання ..


Версія Swift 3.0:

let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey: nil)
_ = self.navigationController?.popToRootViewController(animated: false)

34
+1, це дійсно найбільш розумне рішення. Лише незначна примітка для майбутніх відвідувачів: Animated:NOважлива частина. Якщо YESце передано, анімації змішуються та викликають кумедні ефекти.
DarkDust

12
Найкраще рішення поки що .. А для початківців не забудьте включити QuartCore (#import <QuartzCore / QuartzCore.h>)
nomann

4
Єдина проблема, з якою у мене є це рішення, полягає в тому, що viewDidAppear висунутого диспетчера викликується одразу після того, як я натискаю його без анімації. Чи є шлях до цього?
Педро Манчено

9
Моя проблема з цим кодом полягає в тому, що кожен вид виглядає миготливим до сірого або білого кольору, коли вони ковзають або виходять.
Кріс

1
перевірено на iOS 7.1.2 та iOS 8.3 - цей код працює чудово і працює добре для методуsetViewControllers:
proff

256

Ось так мені завжди вдавалося виконати це завдання.

Для Push:

MainView *nextView=[[MainView alloc] init];
[UIView  beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
[nextView release];

Для попсу:

[UIView  beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
[UIView commitAnimations];

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelay:0.375];
[self.navigationController popViewControllerAnimated:NO];
[UIView commitAnimations];


Я все ще отримую багато відгуків з цього приводу, тому я збираюся продовжувати і оновлювати його для використання анімаційних блоків, які Apple рекомендував спосіб робити анімацію в будь-якому випадку.

Для Push:

MainView *nextView = [[MainView alloc] init];
[UIView animateWithDuration:0.75
                         animations:^{
                             [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                             [self.navigationController pushViewController:nextView animated:NO];
                             [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
                         }];

Для попсу:

[UIView animateWithDuration:0.75
                         animations:^{
                             [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                             [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
                         }];
[self.navigationController popViewControllerAnimated:NO];

3
Дякую за це Але попс виконується автоматично UINavigationController. Як ви перекриєте таку поведінку, щоб ви могли називати свою власну логіку попсу?
Джошуа Френк

1
@stuckj насправді це працює !!! Ви просто повинні замінити superнаself.navigationController
holierthanthou84

Чи є спосіб отримати слайд зліва замість слайда за замовчуванням праворуч?
шим

Перший взагалі не показує новий погляд. Другий не показує анімацію. Дуже погана відповідь! iOS 7.
Дмитро

2
чому ви дали UIViewControllerім'я підкласу без частини "ViewController". Ця назва більше підходить для UIView.
user2159978

29

для поштовху

CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;

[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController pushViewController:ViewControllerYouWantToPush animated:NO];

для поп

CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;

[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController popViewControllerAnimated:NO];

19

@Magnus відповідь, тільки тоді для Swift (2.0)

    let transition = CATransition()
    transition.duration = 0.5
    transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    transition.type = kCATransitionPush
    transition.subtype = kCATransitionFromTop
    self.navigationController!.view.layer.addAnimation(transition, forKey: nil)
    let writeView : WriteViewController = self.storyboard?.instantiateViewControllerWithIdentifier("WriteView") as! WriteViewController
    self.navigationController?.pushViewController(writeView, animated: false)

Деякі сайти:

Ви можете це зробити також із Segue, просто реалізуйте це в prepareForSegueабо shouldPerformSegueWithIdentifier. Однак це також збереже анімацію за замовчуванням. Щоб виправити це, вам потрібно перейти на табло, клацніть Segue і зніміть прапорець "Анімації". Але це обмежить ваш додаток для IOS 9.0 і вище (принаймні, коли я це робив у Xcode 7).

Виконуючи сегу, останні два рядки слід замінити на:

self.navigationController?.popViewControllerAnimated(false)

Незважаючи на те, що я встановив неправду, він ігнорує це.


Як видалити чорний колір у фоновому режимі в кінці анімації.
Мадху

Не працює для анімації контролера push-перегляду, працює для контролера Pop View
Mukul More

16

Пам'ятайте , що в Swift , розширення , безумовно , ваші друзі!

public extension UINavigationController {

    /**
     Pop current view controller to previous view controller.

     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func pop(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.popViewControllerAnimated(false)
    }

    /**
     Push a new view controller on the view controllers's stack.

     - parameter vc:       view controller to push.
     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func push(viewController vc: UIViewController, transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.pushViewController(vc, animated: false)
    }

    private func addTransition(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        let transition = CATransition()
        transition.duration = duration
        transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        transition.type = type
        self.view.layer.addAnimation(transition, forKey: nil)
    }

}

11

Використання приватних дзвінків є поганою ідеєю, оскільки Apple більше не схвалює додатки, які роблять це. Можливо, ви могли б спробувати це:

//Init Animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.50];


[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.navigationController.view cache:YES];

//Create ViewController
MyViewController *myVC = [[MyViewController alloc] initWith...];

[self.navigationController pushViewController:myVC animated:NO];
[myVC release];

//Start Animation
[UIView commitAnimations];

Це лише "половина" працює - це не вирішить складнішої проблеми поп-анімації.
Адам

Мені подобається це рішення краще, і так, воно працює. Використання приватних методів точно відхилить вас.
Бенджамін Інталь

@nicktmro - приватний дзвінок api. Я не помітив жодного.
Франклін

@Franklin деякий час тому тут обговорювалося питання про використання, -pushViewController:transition:forceImmediate:яке було б поганою ідеєю.
nicktmro

9

Оскільки це найкращий результат в Google, я подумав, що поділюсь тим, що, на мою думку, є найбільш розумним способом; що полягає у використанні перехідного API iOS 7+. Я реалізував це для iOS 10 із Swift 3.

Досить просто поєднувати це з тим, як UINavigationControllerанімувати між двома контролерами перегляду, якщо ви створюєте підклас UINavigationControllerі повертаєте екземпляр класу, який відповідає UIViewControllerAnimatedTransitioningпротоколу.

Наприклад, ось мій UINavigationControllerпідклас:

class NavigationController: UINavigationController {
    init() {
        super.init(nibName: nil, bundle: nil)

        delegate = self
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension NavigationController: UINavigationControllerDelegate {

    public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return NavigationControllerAnimation(operation: operation)
    }

}

Ви можете бачити, що я встановлюю UINavigationControllerDelegateсобі, і в розширенні на своєму підкласі я реалізую метод, UINavigationControllerDelegateякий дозволяє повернути користувальницький контролер анімації (тобто NavigationControllerAnimation). Цей спеціальний контролер анімації замінить вам запас анімації.

Вам, напевно, цікаво, чому я передаю в операції NavigationControllerAnimationекземпляр через його ініціалізатор. Я роблю це для того, щоб в процесі NavigationControllerAnimationреалізації UIViewControllerAnimatedTransitioningпротоколу я знав, що таке операція (тобто, "push" або "pop"). Це допомагає дізнатися, якою анімацією я повинен займатися. Більшу частину часу ви хочете виконати іншу анімацію залежно від операції.

Решта - це досить стандартно. Реалізуйте дві необхідні функції в UIViewControllerAnimatedTransitioningпротоколі та анімуйте, як вам подобається:

class NavigationControllerAnimation: NSObject, UIViewControllerAnimatedTransitioning {

    let operation: UINavigationControllerOperation

    init(operation: UINavigationControllerOperation) {
        self.operation = operation

        super.init()
    }

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.3
    }

    public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
            let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return }
        let containerView = transitionContext.containerView

        if operation == .push {
            // do your animation for push
        } else if operation == .pop {
            // do your animation for pop
        }
    }
}

Важливо пам’ятати, що для кожного різного типу операцій (тобто, «push» або «pop») контролери перегляду до і від перегляду будуть різними. Коли ви працюєте з натисканням, контролером для перегляду буде той, який натискається. Коли ви перебуваєте в операції "pop", контролером view буде той, до якого переходить, а контролером з view буде той, що вискакує.

Також toконтролер подання повинен бути доданий як підпогляд containerViewпереходу в контексті переходу.

Коли ваша анімація завершиться, потрібно зателефонувати transitionContext.completeTransition(true). Якщо ви робите інтерактивний перехід, вам доведеться динамічно повертати Boolв completeTransition(didComplete: Bool)залежності від того, якщо перехід був завершений в кінці анімації.

Нарешті ( необов’язкове читання ), ви можете побачити, як я здійснив перехід, над яким працював. Цей код трохи хитріший, і я написав його досить швидко, тому я б не сказав, що це відмінний анімаційний код, але він все ще показує, як робити анімаційну частину.

Шахта була дійсно простим переходом; Я хотів наслідувати ту саму анімацію, яку зазвичай робить UINavigationController, але замість анімації "Наступна сторінка вгорі" це я хотів реалізувати анімацію старого контролера перегляду 1: 1 одночасно з новим поданням з'являється контролер. Це призводить до того, що два контролери подання виглядають так, ніби вони прив'язані один до одного.

Для операції натискання, що вимагає спочатку встановити початок toViewControllerподання виду на екрані осі x, додати його як підвид зображення containerView, анімувати його на екрані, встановивши його origin.xв нуль. У той же час, я анімую fromViewControllerпогляд, origin.xвідтворюючи його на екрані:

toViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.size.width, dy: 0.0)

containerView.addSubview(toViewController.view)

UIView.animate(withDuration: transitionDuration(using: transitionContext),
               delay: 0,
               options: [ UIViewAnimationOptions.curveEaseOut ],
               animations: {
                toViewController.view.frame = containerView.bounds
                fromViewController.view.frame = containerView.bounds.offsetBy(dx: -containerView.frame.size.width, dy: 0)
},
               completion: { (finished) in
                transitionContext.completeTransition(true)
})

Операція естради - це в основному зворотна. Додайте toViewControllerяк підвид з containerView, і анімувати геть fromViewControllerвправо , як анімувати в toViewControllerзліва:

containerView.addSubview(toViewController.view)

UIView.animate(withDuration: transitionDuration(using: transitionContext),
               delay: 0,
               options: [ UIViewAnimationOptions.curveEaseOut ],
               animations: {
                fromViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.width, dy: 0)
                toViewController.view.frame = containerView.bounds
},
               completion: { (finished) in
                transitionContext.completeTransition(true)
})

Ось суть із усім швидким файлом:

https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be


Чудово !. Що я хотів зробити, це ДИВІТЬ анімувати у зворотному напрямку. Я вивчив деякі інші рішення, але всі проявляли миготіння на лівому та правому екрані. Схоже, неявну анімацію зміни альфа не можна видалити за допомогою них. Тільки це рішення вирішило це питання.
бешио

так, це єдине правильне сучасне рішення. (Я не проти, але це точно те саме рішення, яке я набрав нижче! :))
Fattie

@AlanZeino Що робити, якщо всередині того самого ViewController вам потрібно мати диференційовані анімації для різного натискання кнопки? Отже, для кнопки1 потрібна анімація, що розпускається, для кнопки2 потрібен перехід за замовчуванням.
jzeferino

7

Там є UINavigationControllerDelegate та UIViewControllerAnimatedTransitioning, ви можете змінити анімацію на все, що завгодно.

Наприклад, це вертикальна поп-анімація для VC:

@objc class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {

func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
    return 0.5
}

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {

    let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
    let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
    let containerView = transitionContext.containerView()
    let bounds = UIScreen.mainScreen().bounds
    containerView!.insertSubview(toViewController.view, belowSubview: fromViewController.view)
    toViewController.view.alpha = 0.5

    let finalFrameForVC = fromViewController.view.frame

    UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
        fromViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.height)
        toViewController.view.alpha = 1.0
        }, completion: {
            finished in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
    })
}

}

І потім

func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    if operation == .Pop {
        return PopAnimator()
    }
    return nil;
}

Корисний підручник https://www.objc.io/isissue/5-ios7/view-controller-transitions/


6

На основі відповіді, оновленої для швидкої 4jordanperry

Для поштовху UIViewController

let yourVC = self.storyboard?.instantiateViewController(withIdentifier: "yourViewController") as! yourViewController
    UIView.animate(withDuration: 0.75, animations: {() -> Void in
    UIView.setAnimationCurve(.easeInOut)
    self.navigationController?.pushViewController(terms, animated: true)
    UIView.setAnimationTransition(.flipFromRight, for: (self.navigationController?.view)!, cache: false)
})

Для попу

UIView.animate(withDuration: 0.75, animations: {() -> Void in
    UIView.setAnimationCurve(.easeInOut)
    UIView.setAnimationTransition(.flipFromLeft, for: (self.navigationController?.view)!, cache: false)
})
navigationController?.popViewController(animated: false)

5

Ось як я це зробив у Swift:

Для Push:

    UIView.animateWithDuration(0.75, animations: { () -> Void in
        UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
        self.navigationController!.pushViewController(nextView, animated: false)
        UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromRight, forView: self.navigationController!.view!, cache: false)
    })

Для попсу:

Насправді я зробив це дещо інакше на деякі відповіді вище - але, як я новачок у розробці Swift, це може бути неправильним. Я змінив viewWillDisappear:animated:і додав поп-код туди:

    UIView.animateWithDuration(0.75, animations: { () -> Void in
        UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
        UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false)
    })

    super.viewWillDisappear(animated)

5

@Luca Davanzo відповідь у Swift 4.2

public extension UINavigationController {

    /**
     Pop current view controller to previous view controller.

     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func pop(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.popViewController(animated: false)
    }

    /**
     Push a new view controller on the view controllers's stack.

     - parameter vc:       view controller to push.
     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func push(viewController vc: UIViewController, transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.pushViewController(vc, animated: false)
    }

    private func addTransition(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        let transition = CATransition()
        transition.duration = duration
        transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        transition.type = type
        self.view.layer.add(transition, forKey: nil)
    }

}

4

Я нещодавно намагався зробити щось подібне. Я вирішив, що мені не сподобається розсувна анімація UINavigationController, але я також не хотів робити анімації, які UIView дає вам скручування або щось подібне. Я хотів зробити перехресне зникання між поглядами, коли я натискаю чи спливу.

Проблема там пов'язана з тим, що вигляд буквально видаляє подання або проскакує один над вершиною поточного, тому зникнення не працює. Я вирішив взяти новий погляд і додати його як підвід до поточного виду зверху на стеку UIViewController. Я додаю його з альфа-нуля 0, потім роблю перехресне перетворення. Коли послідовність анімації закінчується, я натискаю погляд на стек, не анімуючи його. Потім я повертаюся до старого topView і очищую зміни, які я змінив.

Це трохи складніше, ніж це, тому що у вас є навігаційні елементи, які ви повинні налаштувати, щоб перехід виглядав правильним. Крім того, якщо ви робите якісь обертання, то вам доведеться коригувати розміри кадру, додаючи перегляди у вигляді підвід, щоб вони відображалися правильно на екрані. Ось деякі з кодів, які я використовував. Я підкласифікував UINavigationController і переміняв методи push та pop.

-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
      UIViewController *currentViewController = [self.viewControllers lastObject];
      //if we don't have a current controller, we just do a normal push
      if(currentViewController == nil)
      {
         [super pushViewController:viewController animated:animated];
         return;
      }
      //if no animation was requested, we can skip the cross fade
      if(!animation)
      {
         [super pushViewController:viewController animated:NO];
         return;
      }
      //start the cross fade.  This is a tricky thing.  We basically add the new view
//as a subview of the current view, and do a cross fade through alpha values.
//then we push the new view on the stack without animating it, so it seemlessly is there.
//Finally we remove the new view that was added as a subview to the current view.

viewController.view.alpha = 0.0;
//we need to hold onto this value, we'll be releasing it later
    NSString *title = [currentViewController.title retain];

//add the view as a subview of the current view
[currentViewController.view addSubview:viewController.view];
[currentViewController.view bringSubviewToFront:viewController.view];
UIBarButtonItem *rButtonItem = currentViewController.navigationItem.rightBarButtonItem;
UIBarButtonItem *lButtonItem = currentViewController.navigationItem.leftBarButtonItem;

NSArray *array = nil;

//if we have a right bar button, we need to add it to the array, if not, we will crash when we try and assign it
//so leave it out of the array we are creating to pass as the context.  I always have a left bar button, so I'm not checking to see if it is nil. Its a little sloppy, but you may want to be checking for the left BarButtonItem as well.
if(rButtonItem != nil)
    array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,rButtonItem,nil];
else {
    array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,nil];
}

//remove the right bar button for our transition
[currentViewController.navigationItem setRightBarButtonItem:nil animated:YES];
//remove the left bar button and create a backbarbutton looking item
//[currentViewController.navigationItem setLeftBarButtonItem:nil animated:NO];

//set the back button
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:title style:kButtonStyle target:self action:@selector(goBack)];
[currentViewController.navigationItem setLeftBarButtonItem:backButton animated:YES];
[viewController.navigationItem setLeftBarButtonItem:backButton animated:NO];
[backButton release];

[currentViewController setTitle:viewController.title];

[UIView beginAnimations:@"push view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePushDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[viewController.view setAlpha: 1.0];
[UIView commitAnimations];
}

-(void)animationForCrossFadePushDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{

UIViewController *c = [(NSArray*)context objectAtIndex:0];
UIViewController *n = [(NSArray*)context objectAtIndex:1];
NSString *title     = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:3];
UIBarButtonItem *r = nil;
//not all views have a right bar button, if we look for it and it isn't in the context,
//we'll crash out and not complete the method, but the program won't crash.
//So, we need to check if it is there and skip it if it isn't.
if([(NSArray *)context count] == 5)
    r = [(NSArray *)context objectAtIndex:4];

//Take the new view away from being a subview of the current view so when we go back to it
//it won't be there anymore.
[[[c.view subviews] lastObject] removeFromSuperview];
[c setTitle:title];
[title release];
//set the search button
[c.navigationItem setLeftBarButtonItem:l animated:NO];
//set the next button
if(r != nil)
    [c.navigationItem setRightBarButtonItem:r animated:NO];


[super pushViewController:n animated:NO];

 }

Як я згадую в коді, у мене завжди є елемент кнопки лівої панелі, тому я не перевіряю, чи є він нульовим, перш ніж помістити його в масив, який я передаю як контекст для делегата анімації. Якщо ви це зробите, ви можете зробити цю перевірку.

Проблема, яку я знайшов, полягала в тому, що якщо ви зламаєтесь взагалі в методі делегування, програма не вийде з ладу. Це просто зупиняє заповнення делегата, але ви не отримуєте жодного попередження.
Тому, оскільки я робив прибирання в тій процедурі делегування, це викликало деяку дивну візуальну поведінку, оскільки вона не закінчувала прибирання.

Кнопка "назад" створює дзвінки методом "goBack", і цей метод просто викликає поп-процедуру.

-(void)goBack
{ 
     [self popViewControllerAnimated:YES];
}

Також ось моя поп-програма.

-(UIViewController *)popViewControllerAnimated:(BOOL)animated
{
    //get the count for the number of viewControllers on the stack
int viewCount = [[self viewControllers] count];
//get the top view controller on the stack
UIViewController *topViewController = [self.viewControllers objectAtIndex:viewCount - 1];
//get the next viewController after the top one (this will be the new top one)
UIViewController *newTopViewController = [self.viewControllers objectAtIndex:viewCount - 2];

//if no animation was requested, we can skip the cross fade
if(!animated)
{
    [super popViewControllerAnimated:NO];
            return topViewController;
}



//start of the cross fade pop.  A bit tricky.  We need to add the new top controller
//as a subview of the curent view controler with an alpha of 0.  We then do a cross fade.
//After that we pop the view controller off the stack without animating it.
//Then the cleanup happens: if the view that was popped is not released, then we
//need to remove the subview we added and change some titles back.
newTopViewController.view.alpha = 0.0;
[topViewController.view addSubview:newTopViewController.view];
[topViewController.view bringSubviewToFront:newTopViewController.view];
NSString *title = [topViewController.title retain];
UIBarButtonItem *lButtonItem = topViewController.navigationItem.leftBarButtonItem;
UIBarButtonItem *rButtonItem = topViewController.navigationItem.rightBarButtonItem;

//set the new buttons on top of the current controller from the new top controller
if(newTopViewController.navigationItem.leftBarButtonItem != nil)
{
    [topViewController.navigationItem setLeftBarButtonItem:newTopViewController.navigationItem.leftBarButtonItem animated:YES];
}
if(newTopViewController.navigationItem.rightBarButtonItem != nil)
{
    [topViewController.navigationItem setRightBarButtonItem:newTopViewController.navigationItem.rightBarButtonItem animated:YES];
}

[topViewController setTitle:newTopViewController.title];
//[topViewController.navigationItem.leftBarButtonItem setTitle:newTopViewController.navigationItem.leftBarButtonItem.title];

NSArray *array = nil;
if(rButtonItem != nil)
    array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,rButtonItem,nil];
else {
    array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,nil];
}


[UIView beginAnimations:@"pop view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePopDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[newTopViewController.view setAlpha: 1.0];
[UIView commitAnimations];
return topViewController;

 }

 -(void)animationForCrossFadePopDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
 {

UIViewController *c = [(NSArray *)context objectAtIndex:0];
//UIViewController *n = [(NSArray *)context objectAtIndex:1];
NSString *title = [(NSArray *)context objectAtIndex:1];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *r = nil;



//Not all views have a right bar button.  If we look for one that isn't there
// we'll crash out and not complete this method, but the program will continue.
//So we need to check if it is therea nd skip it if it isn't.
if([(NSArray *)context count] == 4)
    r = [(NSArray *)context objectAtIndex:3];

//pop the current view from the stack without animation
[super popViewControllerAnimated:NO];

//if what was the current veiw controller is not nil, then lets correct the changes
//we made to it.
if(c != nil)
{
    //remove the subview we added for the transition
    [[c.view.subviews lastObject] removeFromSuperview];
    //reset the title we changed
    c.title = title;
    [title release];
    //replace the left bar button that we changed
    [c.navigationItem setLeftBarButtonItem:l animated:NO];
    //if we were passed a right bar button item, replace that one as well
    if(r != nil)
        [c.navigationItem setRightBarButtonItem:r animated:NO];
    else {
        [c.navigationItem setRightBarButtonItem:nil animated:NO];
    }


 }
}

Це майже все. Вам потрібен додатковий код, якщо ви хочете здійснити обертання. Вам потрібно буде встановити розмір кадру ваших поглядів, який ви додаєте як підзагляди, перш ніж їх показувати, інакше ви будете стикатися з проблемами, на які орієнтується пейзаж, але останній раз, коли ви бачили попередній вигляд, це був портрет. Отже, тоді ви додаєте його як підпогляд і розтушовуєте його, але він відображається як портрет, тоді, коли ми з'являємося без анімації, той самий вигляд, але той, що знаходиться в стеці, зараз - пейзаж. Вся справа виглядає трохи кумедно. Кожна реалізація обертання трохи відрізняється, тому я сюди не включив свій код.

Сподіваюся, це допомагає деяким людям. Я шукав все на щось подібне і нічого не міг знайти. Я не думаю, що це ідеальна відповідь, але для мене це справді добре працює.


Незважаючи на захоплення, це, чесно кажучи, не є рішенням на 7 років пізніше!
Fattie

Ти правий. Ця відповідь була з 2011 року. Тоді вона працювала, але з того часу все сильно змінилося. =)
грузинець

4

Тепер ви можете використовувати UIView.transition. Зауважте, що animated:false. Це працює з будь-яким варіантом переходу, заміною pop, push або stack.

if let nav = self.navigationController
{
    UIView.transition(with:nav.view, duration:0.3, options:.transitionCrossDissolve, animations: {
        _ = nav.popViewController(animated:false)
    }, completion:nil)
}

1
@Fattie, цей конкретний метод працює лише з будь-якою зі стандартних анімацій, таких як фліп і локони, перелічені в developer.apple.com/documentation/uikit/uiviewanimationoptions
Peter DeWeese

3

Використовуючи відповідь iJordan як натхнення, чому б просто не створити Категорію на UINavigationController, щоб використовувати її у всьому додатку, а не копіювати / вставляти цей анімаційний код повсюдно?

UINavigationController + Animation.h

@interface UINavigationController (Animation)

- (void) pushViewControllerWithFlip:(UIViewController*) controller;

- (void) popViewControllerWithFlip;

@end

UINavigationController + Animation.m

@implementation UINavigationController (Animation)

- (void) pushViewControllerWithFlip:(UIViewController *) controller
{
    [UIView animateWithDuration:0.50
                     animations:^{
                         [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                         [self pushViewController:controller animated:NO];
                         [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
                     }];
}

- (void) popViewControllerWithFlip
{
    [UIView animateWithDuration:0.5
                     animations:^{
                         [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                         [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
                     }];

    [self popViewControllerAnimated:NO];
}

@end

Потім просто імпортуйте файл UINavigationController + Animation.h і зателефонуйте йому нормально:

[self.navigationController pushViewControllerWithFlip:[[NewViewController alloc] init]];

[self.navigationController popViewControllerWithFlip];

Розумний. Але чому б не додати push / pop методи, які беруть аргумент UIViewAnimationTransition, а не жорсткий код для flipFromRight?
Джеф

@Jef - це зручні методи - таким чином, реалізатору не потрібно пам’ятати, яке значення UIViewAnimationTransition передавати для кожного конкретного типу анімації, вони просто називають метод із «англійським» ім'ям того, що вони хочуть досягти.
DiscDev

@Jef також, ваша пропозиція, безумовно, справедлива - якби я все ще використовував aim-c і потрібен для підтримки багатьох стилів переходу (напевно, не рекомендується, оскільки багато різних стилів переходу збиватимуть з пантелику користувачів), я мав би 1 метод, який приймає тип UIViewAnimationTransition, тоді кілька зручних методів для полегшення розвитку.
DiscDev

3

Це дуже просто

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft

Ласкаво просимо до StackOverflow: якщо ви розміщуєте код, XML або зразки даних, будь ласка, виділіть ці рядки в текстовому редакторі та натисніть кнопку "зразки коду" ({}) на панелі інструментів редактора або використовуючи Ctrl + K на клавіатурі для гарного форматування і синтаксис виділити це!
WhatsThePoint

2

Погляньте на ADTransitionController , краплю заміни на UINavigationController з анімацією користувальницьких переходів (його API відповідає API UINavigationController), який ми створили в Applidium.

Ви можете використовувати різні заздалегідь визначені анімації для натискання та поп- дій, таких як Swipe , Fade , Cube , Carrousel , Zoom тощо.


2

Хоча всі відповіді тут чудові і більшість працює дуже добре, є трохи простіший метод, який досягає того ж ефекту ...

Для Push:

  NextViewController *nextViewController = [[NextViewController alloc] init];

  // Shift the view to take the status bar into account 
  CGRect frame = nextViewController.view.frame;
  frame.origin.y -= 20;
  frame.size.height += 20;
  nextViewController.view.frame = frame;

  [UIView transitionFromView:self.navigationController.topViewController.view toView:nextViewController.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:^(BOOL finished) {
    [self.navigationController pushViewController:nextViewController animated:NO];
  }];

Для попсу:

  int numViewControllers = self.navigationController.viewControllers.count;
  UIView *nextView = [[self.navigationController.viewControllers objectAtIndex:numViewControllers - 2] view];

  [UIView transitionFromView:self.navigationController.topViewController.view toView:nextView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
    [self.navigationController popViewControllerAnimated:NO];
  }];}

Це призведе до збоїв, коли з'явиться кореневий контролер перегляду.
Абдулла Умер

1

Мені невідомо, яким чином можна публічно змінити анімацію переходу.

Якщо кнопка "назад" не потрібна, ви повинні використовувати контролери модального перегляду, щоб мати переходи "натискання знизу" / "перевернути" / "згасати" / (≥3.2) "згортання сторінки".


На приватній стороні метод -pushViewController:animated:викликає незадокументований метод -pushViewController:transition:forceImmediate:, тому, наприклад, якщо ви хочете перехід зліва направо, ви можете використовувати

[navCtrler pushViewController:ctrler transition:10 forceImmediate:NO];

Однак ви не можете змінити "поп" перехід таким чином.


1

Дивіться мою відповідь на це запитання, як це зробити в набагато меншій кількості рядків коду. Цей метод дозволяє анімувати псевдо- "натисненням" нового контролера перегляду будь-яким способом, який вам подобається, і коли анімація виконана, він встановлює контролер навігації так само, як якщо б ви використовували стандартний метод Push. Мій приклад дозволяє анімувати або слайд ліворуч або праворуч. Код, повторений тут для зручності:

-(void) showVC:(UIViewController *) nextVC rightToLeft:(BOOL) rightToLeft {
    [self addChildViewController:neighbor];
    CGRect offscreenFrame = self.view.frame;
    if(rightToLeft) {
        offscreenFrame.origin.x = offscreenFrame.size.width * -1.0;
    } else if(direction == MyClimbDirectionRight) {
        offscreenFrame.origin.x = offscreenFrame.size.width;
    }
    [[neighbor view] setFrame:offscreenFrame];
    [self.view addSubview:[neighbor view]];
    [neighbor didMoveToParentViewController:self];
    [UIView animateWithDuration:0.5 animations:^{
        [[neighbor view] setFrame:self.view.frame];
    } completion:^(BOOL finished){
        [neighbor willMoveToParentViewController:nil];
        [neighbor.view removeFromSuperview];
        [neighbor removeFromParentViewController];
        [[self navigationController] pushViewController:neighbor animated:NO];
        NSMutableArray *newStack = [[[self navigationController] viewControllers] mutableCopy];
        [newStack removeObjectAtIndex:1]; //self, just below top
        [[self navigationController] setViewControllers:newStack];
    }];
}

0

Перегляньте цей варіант у прикладі програми. https://github.com/mpospese/MPFoldTransition/

#pragma mark - UINavigationController(MPFoldTransition)

@implementation UINavigationController(MPFoldTransition)

//- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
- (void)pushViewController:(UIViewController *)viewController foldStyle:(MPFoldStyle)style
{
    [MPFoldTransition transitionFromViewController:[self visibleViewController] 
                                  toViewController:viewController 
                                          duration:[MPFoldTransition defaultDuration]  
                                             style:style 
                                        completion:^(BOOL finished) {
                                            [self pushViewController:viewController animated:NO];
                                        }
     ];
}

- (UIViewController *)popViewControllerWithFoldStyle:(MPFoldStyle)style
{
    UIViewController *toController = [[self viewControllers] objectAtIndex:[[self viewControllers] count] - 2];

    [MPFoldTransition transitionFromViewController:[self visibleViewController] 
                                  toViewController:toController 
                                          duration:[MPFoldTransition defaultDuration] 
                                             style:style
                                        completion:^(BOOL finished) {
                                            [self popViewControllerAnimated:NO];
                                        }
     ];

    return toController;
}

0

Просто використовуйте:

ViewController *viewController = [[ViewController alloc] init];

UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
navController.navigationBarHidden = YES;

[self presentViewController:navController animated:YES completion: nil];
[viewController release];
[navController release];

0

Усвідомлення цього - старе питання. Я все-таки хотів би опублікувати цю відповідь, оскільки у мене виникли деякі проблеми з вискакуванням декількох viewControllersіз запропонованими відповідями. Моє рішення - підкласUINavigationController та переосмислення всіх методів pop та push.

FlippingNavigationController.h

@interface FlippingNavigationController : UINavigationController

@end

FlippingNavigationController.m:

#import "FlippingNavigationController.h"

#define FLIP_DURATION 0.5

@implementation FlippingNavigationController

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [UIView transitionWithView:self.view
                      duration:animated?FLIP_DURATION:0
                       options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromRight
                    animations:^{ [super pushViewController:viewController
                                                   animated:NO]; }
                    completion:nil];
}

- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
    return [[self popToViewController:[self.viewControllers[self.viewControllers.count - 2]]
                             animated:animated] lastObject];
}

- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
{
    return [self popToViewController:[self.viewControllers firstObject]
                            animated:animated];
}

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    __block NSArray* viewControllers = nil;

    [UIView transitionWithView:self.view
                      duration:animated?FLIP_DURATION:0
                       options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromLeft
                    animations:^{ viewControllers = [super popToViewController:viewController animated:NO]; }
                    completion:nil];

    return viewControllers;
}

@end

0

Я знаю, що ця нитка стара, але я думав, що я вкладу свої два центи. Вам не потрібно робити власну анімацію, є простий (можливо, гакітний) спосіб зробити це. Замість того, щоб використовувати push, створіть новий контролер навігації, зробіть новий контролер перегляду кореневим контролером цього контролера nav, а потім подайте навігаційний контролер з оригінального контролера navi. Презентація легко налаштовується з багатьма стилями, і не потрібно робити власну анімацію.

Наприклад:

UIViewcontroller viewControllerYouWantToPush = UIViewController()
UINavigationController newNavController = UINavigationController(root: viewControllerYouWantToView)
newNavController.navBarHidden = YES;
self.navigationController.present(newNavController)

І ви можете змінити стиль презентації, як хочете.


-1

Я знайшов м'яко рекурсивний спосіб зробити це, що працює для моїх цілей. У мене є змінний примірник BOOL, який я використовую для блокування звичайної анімації, що з'являється, і підміни мого власного неанімованого поп-повідомлення. Спочатку змінна встановлюється в NO. Після натискання кнопки "назад" метод делегування встановлює значення "ТАК" і надсилає нове неанімоване поп-повідомлення на панель навігації, тим самим знову викликаючи той самий метод делегата, на цей раз зі змінною, встановленою "ТАК". Якщо змінна встановлена ​​"ТАК", метод делегата встановлює її "НІ" та повертає "ТАК", щоб дозволити виникненню неанімованих поп. Після того, як другий виклик делегата повернеться, ми опиняємося назад у першому, де НІ не повертається, блокуючи оригінальний анімований поп! Насправді це не так безладно, як це звучить. Мій метод shouldPopItem виглядає так:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item 
{
    if ([[navigationBar items] indexOfObject:item] == 1) 
    {
        [expandedStack restack];    
    }

    if (!progPop) 
    {
        progPop = YES;
        [navBar popNavigationItemAnimated:NO];
        return NO;
    }
    else 
    {
        progPop = NO;
        return YES;
    }
}

Працює для мене.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.