Hiding Bottom Bar When Pushed - Animation freezing on Iphone X - swift

I've encountered an issue that I cannot figure out for the life of me. The issue is only happening on the iPhone X. I've added a little video since its difficult to explain exactly whats happening.
I've also added a screenshot of my storyboard so you can see the flow.
Pretty much were experiencing a freeze when the tab bar is being hidden. It only happens when we visit the category VC (which is presented modally using a segue, it is also embedded in a navigation controller.)
** I'm still new to iOS development so if I'm doing anything terribly wrong feel free to share :)
Video:
https://youtu.be/HC14zFxh-HM
Code that sends to reader:
#IBAction func sendToReader(_ sender: Any) {
let myVC = storyboard?.instantiateViewController(withIdentifier: "ReaderRootVC") as! ReaderRootVC
myVC.book = self.book
myVC.hidesBottomBarWhenPushed = true
navigationController?.pushViewController(myVC, animated: true)
}
Code that closes Category VC:
#IBAction func navigationCancelBtnPressed(_ sender: Any) {
self.navigationController?.dismiss(animated: false, completion: nil)
}
Storyboard:

In your ReaderRootVC,
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Hide the Tab Bar
self.tabBarController?.tabBar.isHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Show the Tab Bar
self.tabBarController?.tabBar.isHidden = false
}

Related

How to automatically play audio when entering a screen

I am learning Swift, and I am throwing myself in the deep end by building an app to force myself to learn the language. Something I want to implement is that when I transfer from screen 1 to screen 2, audio is played without needing to do anything. The audio is A-14a. The code below is set up so that when I click the Directions button, it plays the audio, but I don't know how to do it right away. Images are below to help show what I mean.
1st Screen 2nd Screen
The code I have is below:
import UIKit
import AVFoundation
class Intervention_Numerals1: UIViewController {
#IBOutlet weak var Directions: UIButton!
#IBOutlet weak var Done: UIButton!
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
setUpElements()
//Audio Test
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "A-N14a", ofType:"mp3")!))
audioPlayer.prepareToPlay()
} catch {
print(error)
}
}
func setUpElements() {
// Style the elements
Utilities.styleFilledButton(Directions)
Utilities.styleFilledButton(Done)
}
#IBAction func Play(_ sender: Any) {
audioPlayer.play()
}
}
Please let me know any advice on how to do this
Use this method
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
audioPlayer.play()
}
There are a couple of methods you can override to know the state of the view controller and add code to run when that state occurs.
As mentioned by Amila what you are looking for is probably viewDidAppear,
Another method that might also help you achieve this is viewWillAppear
I will provide the code of most of these methods and what they do below so you can try them all and experiment with them:
override func viewDidLoad() {
//This is what you already use every time, its called when the VC's view is loaded into memory
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//This happens when the view is *about* to appear, it happens before users see anything from the view, great for updating UI
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//This happens when the view actually appears on screen and when all the animations of loading the View Controller are over.
//not so good for updating UI since users will see a glimpse of previous view data.
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
//This happens when the view is *about* to disappear, great example for this is when you begin swiping to go back in a navigation controller.
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
//This happens when view is no longer on screen for user to see, like when that swiping back animation in using navigation controller is *finished*
}
There are a few more of these such as viewWillLayoutSubviews and viewDidLayoutSubviews that occur when views inside your View Controller change.
Use all these to control when what happens, hope this helps :)

Prefer large titles for single View Controller

How can I set a large title for a single View Controller embedded in a Navigation Controller?
Normally I am only able to set large titles for a whole Navigation Controller including all View Controllers but i only want one to display a large title.
self.navigationController?.navigationBar.prefersLargeTitles = true
While both of the other answers do the trick, a cleaner way to achieve this is by doing the following in the UIViewController you want:
navigationItem.largeTitleDisplayMode = .always
or for the reverse effect:
navigationItem.largeTitleDisplayMode = .never
This takes away the need to keep track of a state, which in my opinion is a vast improvement
See largeTitleDisplayMode - Apple Developer Documentation for more information on the subject
Only downside is that it's iOS 11.0+
You can set prefersLargeTitles = true in viewWillAppear when ViewController's going to appear and prefersLargeTitles = false in viewWillDisappear when ViewController's going to disappear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.prefersLargeTitles = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.prefersLargeTitles = false
}
You can implement the logic for this viewController only. You can try something like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.prefersLargeTitles = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.navigationBar.prefersLargeTitles = false
}

Presenting a specific view controller once immediately after launch screen closes

I have a 1-screen tutorial View Controller. I want this tutorial VC to show only once (userdefaults), but I want a smooth transition from when the launch screen finishes -> tutorial VC.
Right now - the launch screen finishes, then the main interface of the App shows for a split second, then the tutorial VC shows up. I want to remove this "flashing" of the main interface.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !didShowTut {
fireTutorial()
didShowTut = true
}
}
func fireTutorial() {
let tutVC = UIStoryboard(name: "FirstTutorial", bundle: nil).instantiateViewController(withIdentifier: "TutorialSBID") as UIViewController
tutVC.modalPresentationStyle = .overCurrentContext
self.present(tutVC, animated: false, completion: nil)
}
Any help to create a smooth transition from Launch screen -> Tutorial VC would be appreciated.
You are presenting the tutVC on the viewDidAppear func which is executed after the screen has been loaded.
try calling the fireTutorial() on override func viewDidLoad() or on the override func viewWillAppear(_ animated: Bool) methods that get executed before the screen is loaded.
I went with a slightly different approach to solve this.
I embedded the main app VC in a navigation controller, then pushing the tutorialVC in App Delegate didFinishLaunchingWithOptions.
let tutorialVC = UIStoryboard(name: "FirstTutorial", bundle: nil)
let firstVC = tutorialVC.instantiateViewController(withIdentifier: "TutorialSBID") as UIViewController
if let navigationController = self.window?.rootViewController as? UINavigationController
{
navigationController.pushViewController(firstVC, animated: false)
}
return true
Write this code in viewWillAppear or viewWillLayoutSubviews() instead of viewDidAppear

Black view under navigation bar

In my app i have two view controllers embedded in navigation controller (lets say viewControllerA and viewControllerB). In the rootviewcontroller I don't want to show navigation bar, so in viewWillAppear and viewWillDisappear i have added thes lines:
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(true, animated: false)
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(false, animated: false)
}
Now from the viewcontrollerB when back button is typed under navigation bar (when it starts to disapear) black view appears. How to remove that black view?
P.S. I have set navigation bar isTranslucent to false but it does not solve the problem. In my project i'm not using storyboards.
Below answer based on the question owners test project.
From your test project you disabled the navigationController transition(view to view) animation when you hide and unhide navigation bar that causes the black view to appears.
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(true, animated: true) // set to true
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(false, animated: true) //set to true
}

hide / show tab bar when push / back. swift

Answer:
Use self.tabBarController?.tabBar.hidden instead of hidesBottomBarWhenPushed in each view controller to manage whether the view controller should show a tab bar or not.
override func viewWillAppear(animated: Bool) {
self.tabBarController?.tabBar.hidden = true/false
}
I want
view controller 1: tab bar should be showed
view controller 2: tab bar should be showed
view controller 3: tab bar should not be showed.
view controller 4: tab bar should not be showed.
I wrote
// prepareForSegue in view controller 1,
let upcoming = segue.destinationViewController as! viewcontroller3
upcoming.hidesBottomBarWhenPushed = true
// in view controller 3,
func clickOnButton(button: UIButton) {
self.hidesBottomBarWhenPushed = false
self.performSegueWithIdentifier("viewController2", sender: self)
self.hidesBottomBarWhenPushed = true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "viewController2" {
let upcoming = segue.destinationViewController as! viewController2
upcoming.hidesBottomBarWhenPushed = false
}
}
// prepareForSegue in view controller 2
let upcoming = segue.destinationViewController as! viewController4
upcoming.hidesBottomBarWhenPushed = true
if 1 -> 3 then back to 1, works.
if 1 -> 3 -> 2 then back to 3 and back to 1, works.
if 2 -> 4, then back to 2, works.
if 1 -> 3 -> 2 -> 4 then back to 2, tab bar is not showed. Wondering why. Any suggestions or some explanation of hidesBottomBarWhenPushed as it confuses me a lot
As it's name suggest, hiddenBottomBarWhenPushed only hide bottom bar if needed, it will not unhide bottomBar.
You can do this to get it works:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.hidden = true/false
}
or simply put self.tabBarController?.tabBar.hidden = true/false in prepareForSegue
But I would not recommend you to do so, as it would be weird if bottomBar suddenly popped out, user will thought they suddenly back to rootViewController while they are not.
Users should always know where they are in your app and how to get to their next destination.
Add hidesBottomBarWhenPushed property to destination view controller, and set to true.
Example with push VC with identifier:
let storyboard = UIStoryboard(name: STORYBOARD_NAME, bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: VC_IDENTIFIER) as! YourViewController
vc.hidesBottomBarWhenPushed = true
navigationController?.pushViewController(vc, animated: true)
Here's my two cents.
Swift 3/4/5:
Approach 1: (Recommended)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "YourSegueIdentifier" {
let destinationController = segue.destinationViewController as! YourViewController
destinationController.hidesBottomBarWhenPushed = true // Does all the hide/show work.
}
}
Approach 2:
override func viewWillAppear(_ animated: Bool) { // As soon as vc appears
super.viewWillAppear(true)
self.tabBarController?.tabBar.isHidden = false
}
override func viewWillDisappear(_ animated: Bool) { // As soon as vc disappears
super.viewWillDisappear(true)
self.tabBarController?.tabBar.isHidden = true
}
Add this implementation in ViewController you want to hide/show tabbar on pushed/popped. it will also work for all next pushed view controllers.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if wilmove {
hidesBottomBarWhenPushed = true
}
wilmove = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if wilmove {
hidesBottomBarWhenPushed = false
}
wilmove = false
}
var wilmove = false
override func willMove(toParentViewController parent: UIViewController?) {
super.willMove(toParentViewController: parent)
wilmove = true
if !isViewLoaded {
hidesBottomBarWhenPushed = true
}
}
Swift 5
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
self.hidesBottomBarWhenPushed = true
}
It's Better to Fade Out than to Hide Away...
(.... my my, hey hey)
Swift 5:
Yet another approach... is to fade the tab bar in and out.. Not saying this is strategically more advantageous than any of the other ones, such as those entailing prepareForSegue, but it can be adapted to other triggers. In any case, animating tab bar alpha avoids the harsh disappear/appear effect when setting .isHidden on the tabBar (UIView) by fading it in and out. This is done in any VC that needs it hidden when the VC is pushed or loaded and unhidden when the VC is popped or unloaded.
This will not bother to reinstate the tab bar until the back button is pushed in the navBar or equivalent action, such that when a child VC is pushed onto it, the tab bar will remain hidden, which usually (but doesn't always) makes sense.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UIView.animate(withDuration: 0.4, delay: 0.0, options: UIView.AnimationOptions.curveEaseOut, animations: {
self.tabBarController?.tabBar.alpha = 0.0
}, completion: { (finished: Bool) -> Void in
self.tabBarController?.tabBar.isUserInteractionEnabled = false
})
}
override func viewWillDisappear(_ animated: Bool) {
if self.isMovingFromParent {
UIView.animate(withDuration: 0.4, delay: 0.0, options: UIView.AnimationOptions.curveEaseOut, animations: {
self.tabBarController?.tabBar.alpha = 1.0
}, completion: { (finished: Bool) -> Void in
self.tabBarController?.tabBar.isUserInteractionEnabled = true
})
}
}