Flickering When Closing Coach Marks on Tap
alionaalias opened this issue · 1 comments
I've been using the Instructions library for adding coach marks to my application and it's been great. However, I've stumbled upon an issue where a flicker is observed when closing the coach marks by tapping on them.
Here's a brief description of the issue:
- The flicker happens only when closing the coach marks by tapping on them, and not when closing by tapping elsewhere on the screen.
- I've tried using the
stop(immediately: true)
andstop(immediately: false)
methods to close the coach marks, but the flicker persists.
I have looked through the documentation but couldn't find a solution to this problem. I've tried tweaking the animation properties to attempt to smoothen the transition but to no avail.
I'm attaching a video to demonstrate the flicker. The flicker happens when transitioning from the coach mark to the normal state of the app.
[Video or GIF demonstrating the issue]
Here's the relevant code snippet from my project:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let didShowOnboardingTooltip = UserDefaults.standard.bool(forKey: self.privateFullScreanDidShowOnboardingTooltipKey)
if !didShowOnboardingTooltip {
DispatchQueue.main.asyncAfter(deadline: .now() + 0) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.coachMarksController.start(in: .viewController(strongSelf))
UserDefaults.standard.setValue(true, forKey: strongSelf.privateFullScreanDidShowOnboardingTooltipKey)
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
coachMarksController.stop(immediately: true)
}
func numberOfCoachMarks(for coachMarksController: Instructions.CoachMarksController) -> Int {
2
}
func coachMarksController(_ coachMarksController: CoachMarksController, coachMarkAt index: Int) -> CoachMark {
switch index {
case 0:
return coachMarksController.helper.makeCoachMark(for: sendToPublicBtn)
case 1:
return coachMarksController.helper.makeCoachMark(for: sendToRecommendsBtn)
default:
return coachMarksController.helper.makeCoachMark()
}
}
func coachMarksController(_ coachMarksController: Instructions.CoachMarksController, coachMarkViewsAt index: Int, madeFrom coachMark: Instructions.CoachMark) -> (bodyView: (UIView & Instructions.CoachMarkBodyView), arrowView: (UIView & Instructions.CoachMarkArrowView)?) {
let coachViews = coachMarksController.helper.makeDefaultCoachViews(withArrow: true, arrowOrientation: coachMark.arrowOrientation)
var text: String
var boldSubstring: String?
switch index {
case 0:
text = "Tap here to move this wish to your Public wishlist. Others can see and know what to gift you."
boldSubstring = "Public wishlist"
case 1:
text = "Tap here to show this item in Recommendations from you."
boldSubstring = "Recommendations"
default:
text = ""
}
let attributedText = NSMutableAttributedString(string: text)
let regularFont = UIFont.systemFont(ofSize: 14)
let boldFont = UIFont.boldSystemFont(ofSize: 14)
let regularAttributes: [NSAttributedString.Key: Any] = [.font: regularFont]
attributedText.addAttributes(regularAttributes, range: NSRange(location: 0, length: text.count))
if let boldSubstring = boldSubstring, let range = text.range(of: boldSubstring) {
let nsRange = NSRange(range, in: text)
attributedText.addAttributes([.font: boldFont], range: nsRange)
}
coachViews.bodyView.hintLabel.attributedText = attributedText
coachViews.bodyView.nextLabel.text = "Ok!"
coachViews.bodyView.background.innerColor = UIColor.rgbColorFor(hex: "696DBF")
coachViews.arrowView?.background.innerColor = UIColor.rgbColorFor(hex: "696DBF")
coachViews.bodyView.background.borderColor = UIColor.rgbColorFor(hex: "696DBF")
coachViews.arrowView?.background.borderColor = UIColor.rgbColorFor(hex: "696DBF")
coachViews.bodyView.hintLabel.textColor = .white
coachViews.bodyView.nextLabel.textColor = .white
coachViews.bodyView.separator.translatesAutoresizingMaskIntoConstraints = false
coachViews.bodyView.separator.bottomAnchor.constraint(equalTo: coachViews.bodyView.bottomAnchor, constant: -10).isActive = true
coachViews.bodyView.separator.topAnchor.constraint(equalTo: coachViews.bodyView.topAnchor, constant: 10).isActive = true
coachViews.bodyView.layer.cornerRadius = 10
coachViews.bodyView.clipsToBounds = true
return (bodyView: coachViews.bodyView, arrowView: coachViews.arrowView)
}
func fetchFadeAnimationOfCoachMark(_ coachMark: CoachMark, forIndex index: Int) -> CAAnimationGroup? {
let animationGroup = CAAnimationGroup()
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
fadeAnimation.fromValue = 0.0
fadeAnimation.toValue = 1.0
fadeAnimation.duration = 0.3
animationGroup.animations = [fadeAnimation]
animationGroup.duration = 0.3
return animationGroup
}
func fetchTransitionAnimationOfCoachMark(_ coachMark: CoachMark, forIndex index: Int) -> CAAnimationGroup? {
let animationGroup = CAAnimationGroup()
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
fadeAnimation.fromValue = 0.0
fadeAnimation.toValue = 1.0
fadeAnimation.duration = 0.3
animationGroup.animations = [fadeAnimation]
animationGroup.duration = 0.3
return animationGroup
}
func didTap(in coachMarksController: Instructions.CoachMarksController, at index: Int?) {
coachMarksController.stop(immediately: true)
}
func shouldHandleOverlayTap(in coachMarksController: Instructions.CoachMarksController, at index: Int) -> Bool {
return true
}
func shouldAnimateOverlay(in coachMarksController: Instructions.CoachMarksController, for index: Int) -> Bool {
return false
}
FILE.2023-09-29.16.27.31.mp4
Solved: Flickering Issue on Coach Mark Disappearance
It turned out that the functions fetchFadeAnimationOfCoachMark() and fetchTransitionAnimationOfCoachMark() I implemented above were not being called. A careful review of the documentation helped to correctly implement and call the necessary functions, solving the problem.
I managed to solve the problem by setting the animation duration to 0 in the fetchDisappearanceTransitionOfCoachMark and fetchAppearanceTransitionOfCoachMark methods of the CoachMarksControllerAnimationDelegate protocol. Here's the code snippet:
func coachMarksController(
_ coachMarksController: CoachMarksController,
fetchAppearanceTransitionOfCoachMark coachMarkView: UIView,
at index: Int,
using manager: CoachMarkTransitionManager
) {
manager.parameters.duration = 0
manager.parameters.delay = 0
manager.animate(.regular, animations: { context in
coachMarkView.alpha = 1
})
}
func coachMarksController(
_ coachMarksController: CoachMarksController,
fetchDisappearanceTransitionOfCoachMark coachMarkView: UIView,
at index: Int,
using manager: CoachMarkTransitionManager
) {
manager.parameters.duration = 0
manager.parameters.delay = 0
manager.animate(.regular, animations: { context in
coachMarkView.alpha = 0
})
}
This change eliminated the flickering effect, providing a smoother experience.