Swift - UIScrollView scroll animation not working in viewDidLoad() - swift

I'm developing an application using Storyboard with Xcode 12.3 and I need to animate the scrolling of a scrollView right after the view loads up on the screen and also when the user returns from another view controller to this view. It seems to me that the animation does not work if I scroll the view from within the viewDidLoad() function or from the function called when unwinding a segue. Instead, the view appears on the screen (in both cases) with the scrollview already scrolled at the respective location. Essentially, the end result is achieved but the scrolling animation is seemingly ignored or not executed.
Could anyone please help with this problem?
Here's my simple code that demonstrates the issue in both instances:
override func viewDidLoad {
super.viewDidLoad()
// ...
scrollView.setContentOffset(CGPoint(x: xOffset, y: yOffset), animated: true)
}
#IBAction func myUnwindAction(unwindSegue: UIStoryboardSegue) {
let segue = unwindSegue.source as! AnotherViewController
// ...
scrollView.setContentOffset(CGPoint(x: xOffset, y: yOffset), animated: true)
}
However, if I place the scrolling code in the IBAction of a test button and I click on this button after the view loads up, the view animates the scrolling smoothly and without any issues - Here's the code for this function:
#IBAction func testButton(_ sender: Any) {
scrollView.setContentOffset(CGPoint(x: xOffset, y: yOffset), animated: true)
}
Many thanks.

You need to use in viewDidAppear
you should google "View Controller Life Cycle"
var isFirst = true
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if isFirst {
isFirst = false
scrollView.setContentOffset(CGPoint(x: xOffset, y: yOffset), animated: true)
}
}

Related

How to scroll to the View Controller top and display a Large Title?

I have a ViewControllerOne with a tableView constrained to a superview and filled with a content. User can scroll down some content, then switch to ViewControllerTwo and change tableView data source content on another.
When that happens and user returns to the ViewControllerOne I want the VC to be reset on its initial state at the top with a Large Title and a new content, but with a workaround I found it scrolls only till the tableView top and stops on a Small Title.
Here is the code:
When user picks a new Data Source in ViewControllerTwo I save it as a bool in UserDefaults:
UserDefaults.standard.set(true, forKey: "newDataSourcePicked")
In ViewControllerOne I trigger the scrolling method in a viewWillAppear():
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
scrollVCUp()
}
Here is scrollVCUp(). Here I use the saved bool. Also use delay because its not scrolling without it:
func scrollVCUp() {
if newDataSourcePicked {
traitCollection.verticalSizeClass == .compact ? setVCOffset(with: view.safeAreaInsets.top, and: updateLabelTopInset, delayValue: 0.1) : setVCOffset(with: biggestTopSafeAreaInset, and: updateLabelTopInset, delayValue: 0.1)
UserDefaults.standard.set(false, forKey: "newDataSourcePicked")
}
}
Here is setVCOffset():
func setVCOffset(with viewInset: CGFloat, and labelInset: CGFloat, delayValue: Double = 0.0) {
let firstVC = navigationController?.viewControllers.first as? CurrencyViewController
guard let scrollView = firstVC?.view.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView else { return }
if delayValue > 0.0 {
DispatchQueue.main.asyncAfter(deadline: .now() + delayValue) {
scrollView.setContentOffset(CGPoint(x: 0, y: -(viewInset - labelInset)), animated: true)
}
} else {
scrollView.setContentOffset(CGPoint(x: 0, y: -(viewInset - labelInset)), animated: true)
}
}
I also have a tabBar and when I use the same code to scroll ViewControllerOne by tapping on a tabBar it scrolls and shows a Large Title, but doesn't work if we switch to another VC and back.
Here is a gif:
What should I do to scroll and always show a Large Title?
I found two possible approaches:
Approach 1
Don't use the same UIViewController instance, that holds the UITableView. Create a new one.
(Your case: when ViewControllerOne push ViewControllerTwo).
With this approach you get the "fresh" layout with large title every time you push the VC.
Approach 2
Scroll by calculating the UITableView.contentOffset. Use for that adjustedContentInset.top and round the value.
With this approach you get the same result like approach 1, but with a visible back scrolling animation.
class ViewControllerTwo {
private var _adjustedContentInsetTopRounded: CGFloat?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let y = _adjustedContentInsetTopRounded {
DispatchQueue.main.async {
self.tableView.setContentOffset(
CGPoint(
x: 0,
y: -y
),
animated: true
)
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
_adjustedContentInsetTopRounded = tableView.adjustedContentInset.top.rounded(.up)
}
}

How to fix: UIView animations not restarting when returning to view controller

I have a view that starts animating when a ViewController first opens. It works fine on initial launch, but when returning to the view after leaving the animation does not restart. The views are embedded in a Navigation controller
I tried placing the animation in several locations, including viewWillAppear, viewDidAppear and viewWillLayoutSubviews
I've placed a breakpoint in viewDidAppear and it does step through the code, but the animation does not restart.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
UIView.animate(withDuration: 20.0, delay: 0, options: [.autoreverse, .repeat, .curveLinear], animations: {
self.scrollingView.transform = CGAffineTransform(translationX: -350, y: 0)
}, completion: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
scrollingView.layer.removeAllAnimations()
}
You need to return the scrollingView to its original position during the segue, because of the UINavigationController the 1st UIViewController does not go out of scope, aka it does not get instantiated again. Therefore when you come back to the 1st UIViewController, the scrollingView is still running the previous animation.
You need to restart the CGAffineTransform using the code below :
override func viewDidDisappear(_ animated: Bool) {
self.textView.transform = CGAffineTransform.identity
}

UITabbar jumps when segueing to a controller without a tabbar after modal presentation on iPhone X

I have a Master-Detail application with a tab bar on the Master Controller screen and I have a problem with iPhone X. When I segue from the Master to the Detail, everything works fine. If I show a modal window from the Master first and then go to the Detail, the tab bar freezes with the wrong size for a while. If I fix the tab bar size, everything animates fine. With this, for example:
class MyTabBar: UITabBar {
var tabBarHeight: CGFloat = 83
override func sizeThatFits(_ size: CGSize) -> CGSize {
let superSize = super.sizeThatFits(size)
return CGSize(width: superSize.width, height: self.tabBarHeight)
}
}
However, I don't want to fix the size, as it's too complicated to manage all possible sizes and orientations.
I've found a solution:
class MyTabBarController: UITabBarController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.view.setNeedsLayout()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UIView.animate(withDuration: 0.4) { [weak self] in
self?.view.layoutIfNeeded()
}
}
}

When I go back to a view controller after opening it previously it does not animate labels Xcode Swift

When I open my app I have this label that animates in from outside the screen. When I open the view controller for the first time it works. However, when I go to another view controller and then go back to the initial view controller this label will just be there and not animate in.
levelsLabel.center = CGPoint(x:levelsLabel.center.x - 500, y:levelsLabel.center.y)
UIView.animate(withDuration: 2) {
self.levelsLabel.center = CGPoint(x:self.levelsLabel.center.x + 500, y:self.levelsLabel.center.y)
}
Anyone have any suggestions?? Thank You!
Putting your block of code in viewWillAppear rather than viewDidLoad will make it work. However, according to Apple Doc.
viewDidAppear: Use this method to trigger any operations that need
to occur as soon as the view is presented onscreen, such as fetching
data or showing an animation.
The following is what I'd recommend you to do.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
levelsLabel.center = CGPoint(x:levelsLabel.center.x - 500, y:levelsLabel.center.y)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 2) {
self.levelsLabel.center = CGPoint(x:self.levelsLabel.center.x + 500, y:self.levelsLabel.center.y)
}
}

Swift 3 Popover Dim Background

I have read multiple places with suggestions on how to accomplish this. I went with adding a UI view in the background and setting it to disable and then after showing the popover, setting the view to enable.
As you can see it looks to work nicely:
But I do have two problems. The first one is once the popover is presented, you can tap anywhere on the background to dismiss the popover. Is there anywhere to block this from happening? I assumed my background UIView would block any inputs.
Also, after the popover is dismissed, the screen is still dim. I tried the following but neither of them load after dismissing the popover so the View never gets set back to disable:
override func viewDidAppear(_ animated: Bool) {
dimView.isHidden = true
}
override func viewWillAppear(_ animated: Bool) {
dimView.isHidden = true
}
EDIT:
This is the code that I use to present the popover:
let popover = storyboard?.instantiateViewController(withIdentifier: "PopoverVC")
popover?.modalPresentationStyle = .popover
popover?.popoverPresentationController?.delegate = self as? UIPopoverPresentationControllerDelegate
popover?.popoverPresentationController?.sourceView = self.view
popover?.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popover?.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
dimView.isHidden = false
self.present(popover!, animated: false)
I realize that, dimView is not in PopoverVC, add it into PopoverVC and handle dismiss when tap on it.After the popover is dismissed viewDidAppear and viewWillAppear will not be called. So your screen is still blurry.If you add dimView into Popover, hope you can solve these issuses
I think you could solve your two problems with the UIPopoverPresentationControllerDelegate and a protocol/ delegate to tell the presenting viewcontroller when your are dismissing and hide your dimView.
The first issue can be implemented like this:
extension YourViewController: UIPopoverPresentationControllerDelegate {
func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool {
return false
}
For the second issue you can pass a function through delegation. Hopefully this link will help with that.
https://matteomanferdini.com/how-ios-view-controllers-communicate-with-each-other/
Cheers