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

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()
}
}
}

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)
}
}

Swift - UIScrollView scroll animation not working in viewDidLoad()

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)
}
}

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
}

Animation segue bug on the navigation bar with large title

My bug:
If navigate from a view controller with large titles enabled to a view controller with large titles disabled i see same bug. Height navigation bar changes not smoothy.
I want animation change height navBar during segue on another viewController like this
Common propertyes for navBar set up in BaseNavigationController
class BaseNavigationController: UINavigationController {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad() {
super.viewDidLoad()
setNavBarTitlesPropertyes()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
private func setNavBarTitlesPropertyes() {
navigationBar.tintColor = .white
navigationBar.titleTextAttributes = [
.foregroundColor: UIColor.white
]
if #available(iOS 11.0, *) {
navigationBar.prefersLargeTitles = true
navigationBar.largeTitleTextAttributes = [
.foregroundColor: UIColor.white
]
}
}
And my setting navbar in the storyboard:
I found solution for this trouble. UINavigationBar
property translucent should be true, and also bottom and top constraint for tableView in UIViewController should be equal Superview.Top and Superview.Bottom accordingly.

For iPhone How to set the preferred content size for popovers with UINavigation Controller

I am trying to customize the preferred content size of the popover (which is a UITableViewController with the embeded UINavigation Controller). I implemented the following code in the UITableViewController.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let minimumSize = self.view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
self.preferredContentSize = CGSize(width: 280, height: minimumSize.height)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
It is working fine on the iPad views but not on the iPhone views. for iPhone it is taking over the full screen not presenting as a pop over.
Any help on this would be much apprecaited.
The only explanation is that you didn't set the delegate correctly, so the adaptivePresentationStyleForPresentationController is not called at all