Thank you guys. The reason of errors was the place of the declaration of the next ViewController.
//Screen1.swift
import UIKit
class Screen1: UIViewController, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
let transition = FadePresentAnimator()
//let nextScreen = Screen2() //2nd Screen <--------------- ERROR due to loop in initialization)
let btnSize:CGFloat = 56.0
let btn1 = ClickableButton()
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//happens only once:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Screen 1"
view.backgroundColor = .brown // HexColor.GoldenColors.DarkKhaki
self.navigationController?.delegate = self
}
//happens every time when shown:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setupLayout()
}
private func setupLayout() {
//btn1:
view.addSubview(btn1)
btn1.setDefaultTitle(title: "▶") // ⏹ "▶" "■"
btn1.apply(height: btnSize)
btn1.apply(width: btnSize)
btn1.applyDefaultStyle()
if #available(iOS 11.0, *) {
btn1.alignXCenter(to: view.safeAreaLayoutGuide.centerXAnchor)
} else {
// Fallback on earlier versions
}
if #available(iOS 11.0, *) {
btn1.alignYCenter(to: view.safeAreaLayoutGuide.centerYAnchor)
} else {
// Fallback on earlier versions
}
btn1.clickHandler {
let nextScreen = Screen2() //2nd Screen <--------------- LOCAL
nextScreen.transitioningDelegate = self
//self.present(self.nextScreen, animated: true, completion: nil)
self.navigationController?.pushViewController(nextScreen, animated: true) //false for a custom transition
}
}
//forward
func animationController(forPresented presented: UIViewController,
presenting: UIViewController, source: UIViewController) ->
UIViewControllerAnimatedTransitioning? {
return transition
}
//backward
func animationController(forDismissed dismissed: UIViewController) ->
UIViewControllerAnimatedTransitioning? {
return nil
}
}
//FadePresentAnimator.swift
//FadePresentAnimator.swift
import UIKit
class FadePresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 2.0
var presenting = true
var originFrame = CGRect.zero
var dismissCompletion: (()->Void)?
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
//Setting the transition’s context
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//Adding a fade transition
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
containerView.addSubview(toView)
toView.alpha = 0.0
UIView.animate(withDuration: duration,
animations: {
toView.alpha = 1.0
},
completion: { _ in
transitionContext.completeTransition(true)
}
)
}
}
//ClickableButton.swift
//ClickableButton.swift
import UIKit
class ClickableButton: UIButton {
@objc func setDefaultTitle(title text: String = "Button", color textColor: UIColor = .black) {
self.setTitle(text, for: .normal)
self.setTitleColor(textColor, for: .normal)
}
//Adding a closure as a target to a UIButton:
var action: (() -> Void)?
@objc func triggerClickHandler() { action?() }
/*
Use inside UIViewController:
btn.clickHandler {
self.present(self.secondVC, animated: true, completion: nil)
}
*/
func clickHandler(_ action: @escaping () -> Void) {
self.action = action
self.addTarget(self, action: #selector(ClickableButton.triggerClickHandler), for: .touchUpInside)
}
@objc func applyDefaultStyle(bgColor: UIColor = .white) {
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = bgColor
self.setTitleColor(.black, for: .normal)
self.layer.cornerRadius = 4
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 2)
self.layer.shadowRadius = 5
self.layer.shadowOpacity = 1
//btn.layer.masksToBounds = true
self.titleEdgeInsets.left = 8
self.titleEdgeInsets.right = 8
self.titleEdgeInsets.top = 4
self.titleEdgeInsets.bottom = 4
}
}
Based on another tutorial I have created such a struct which uses CATransition:
struct Transitions {
static func setTransitionTo(view: UIView!, from: CATransitionSubtype = .fromRight) {
let transition = CATransition()
transition.duration = 0.5
transition.type = CATransitionType.push
transition.subtype = from //CATransitionSubtype.fromRight
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
}
static func setFadeTransitionTo(view: UIView!) {
//let transition = SKTransition.fade(withDuration: 0.5)
let transition = CATransition()
transition.duration = 0.5
transition.type = CATransitionType.fade
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
}
}
Usage:
topBtn.clickHandler {
let secondVC = SecondVC() //2nd Screen
secondVC.transitionId = 1
Transitions.setTransitionTo(view: self.view, from: .fromBottom)
self.present(secondVC, animated: false, completion: nil)
}
Now I have idea to combine UIViewControllerAnimatedTransitioning protocol with CATransition... but I don't know how...
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let transition = CATransition()
....
}