I'm in the process of building a walkthrough for my app, and I'm running into some strange behavior.
Some context on the setup: In my Walkthrough storyboard, I have a view controller called TutorialViewController
that has a welcome message, a 'tutorial' button, and a 'skip' button. The 'tutorial' button triggers a segue to my WalkthroughViewController
, which has next/back/close buttons and a UIPageIndicator
floating over a UIContainerView
. Embedded in that UIContainerView
is my WalkthroughPageViewController
. Finally, I have a WalkthroughContentViewController
with two UIImageViews - contentImageView
and contentImageViewMask
- that is used as the data source for the WalkthroughPageViewController
. Through swiping or pressing the navigation buttons, the next or previous view controller is instantiated (though it's really only reusing my WalkthroughContentViewController
).
The goal: I'd ideally like to constantly loop an animation through an array of image strings for each page of the walkthrough. For example, if page 1 has an array of 3 image strings, the contentImageView
and contentImageViewMask
would alternate animating (through alpha values) from imageArray[0]
-> imageArray[1]
-> imageArray[2]
-> imageArray[0]
-> imageArray[1]
etc.
Each page has a different number of image strings in its imageArray
, and I haven't figured out how to smoothly loop through all of them yet. The code I have so far animates between the first two image strings in the array and comes from this answer, which lives in my WalkthroughContentViewController.swift
file:
override func viewDidLoad() {
super.viewDidLoad()
contentImageViewMask.alpha = 0
contentImageView.image = UIImage(named: imageArray[0])
UIView.animate(withDuration: 1.5, delay: 1.5, options: [UIViewAnimationOptions.autoreverse, UIViewAnimationOptions.repeat], animations: {
self.contentImageViewMask.image = UIImage(named: self.imageArray[1])
self.contentImageViewMask.alpha = 1
}, completion: nil)
}
The above sort of works in that, upon pressing the tutorial button, page 1 is immediately set to imageArray[1]
(incorrect behavior), while page 2 smoothly animates between imageArray[0]
and imageArray[1]
(correct behavior). However, swiping more than one page past page 2 deactivates the animation on page 2. Every once in a while, page 1 will start animating correctly at this point, but I can't seem to figure out a reliable way to reproduce this. Here's a video showing the behavior. I took this video prior to hooking up the UIPageIndicator and button actions, so ignore that, but the weird thing is that the page 2 animation only happens through swiping to the page. If you get to page 2 via the next button, the animation doesn't work.
Two things that might be relevant: 1.) In my WalkthroughPageViewController.swift
file, I have implemented the two required methods for UIPageViewControllerDataSource
, viewControllerBefore
and viewControllerAfter
, and 2.) in my WalkthroughPageViewController.swift
file, I have the following code to alert the delegate when the swipe animation has ended and the next page is fully displayed:
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
if let contentViewController = pageViewController.viewControllers?.first as? WalkthroughContentViewController {
currentIndex = contentViewController.index
walkthroughDelegate?.didUpdatePageIndex(currentIndex: contentViewController.index)
}
}
}
Any insight would be greatly appreciated.
For anyone looking to do something similar but with a bit of added transitional flexibility (like having images fade to the next), this answer helped me achieve my animation goal of transitioning between images in a way that wasn't jarring.
@IBOutlet weak var contentImageView: UIImageView!
var index = 0
let animationDuration: TimeInterval = 0.5
let switchingInterval: TimeInterval = 2.0
var imageStringArray: [String] = [""]
var images = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
for string in imageStringArray {
images.append(UIImage(named: string)!)
}
contentImageView.image = images[index]
animateImageView()
}
func animateImageView() {
CATransaction.begin()
CATransaction.setAnimationDuration(animationDuration)
CATransaction.setCompletionBlock {
DispatchQueue.main.asyncAfter(deadline: .now() + self.switchingInterval) {
self.animateImageView()
}
}
let transition = CATransition()
transition.type = kCATransitionFade
contentImageView.layer.add(transition, forKey: kCATransition)
contentImageView.image = images[index]
CATransaction.commit()
index = index < images.count - 1 ? index + 1 : 0
}
It's maybe worth noting that the original answer on that question said that, "Implementing it as a custom image view would be better."
If you only need to animate between images and the transition doesn't matter, then animatedImage
is probably a smarter solution (as matt said above).
contentImageView.image = UIImage.animatedImage(with: images, duration: 3)