Variables don't update in UITabBarControllerDelegate - ios

Here is what I'm trying to do:
When user clicks the 'Compressors' button, it shows a scroll view with details of compressors (or whatever). And to get back to menu, user clicks the 'More' tab bar item.
I've set up a variable first to store information if this is first time user taps the 'More' item.
class MoreVC: UIViewController {
var first : Bool = true
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.isHidden = true
}
open func hideEverything(_ bool : Bool)
{
print(bool)
if bool == true
{
// First time user taps More - nothing happens
return
}
print("hideEverything()")
}
#IBAction func compressorsButton(_ sender: Any) {
scrollView.isHidden = false
print(first)
}
override func viewDidAppear(_ animated: Bool) {
print("-> \(self.title!)")
first = false
}
override func viewDidDisappear(_ animated: Bool) {
scrollView.isHidden = true
first = true
}
}
Then I have Tab bar delegate in different file:
class myTabBarController: UITabBarController, UITabBarControllerDelegate
{
override func viewDidLoad() {
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, didSelect selectedViewController: UIViewController) {
if selectedViewController is MoreVC
{
let more = MoreVC()
print("more.first = \(more.first)")
if more.first == false
{
more.scrollView.isHidden = true
}
}
}
}
So, when I tap compressors button, view shows up, when I switch tabs, view hides. But when scrollView is up and I press 'More', it doesn't trigger the more.scrollView.isHidden = true because it detects that first = true, which it is not! It's set to false (as it should be), but I still get true there. I think what I need to do is somehow update myTabBarController, but I have no idea how.
Please help, any hint on what is going on will be much appreciated!
Also thanks for reading this far, I hope you understood my problem.
EDIT from future me:
This problem could have been resolved more naturally by using navigation controller through my app (which I ended up doing anyway). Joe's answer is correct, but if someone bumps into same problem as me, there is a second way.

Since you are updating the boolean on the newly created MoreVC, the existing one doesn't change at all.
Instead of:
if selectedViewController is MoreVC
update it to:
if let more = selectedViewController as? MoreVC
then get rid of the line where you create a brand new MoreVC.

Related

iOS delegate is nil when trying to pass data back with Swift 5

I know this is a pretty common question but I've tried the various solutions offered here (that are not too old) and in numerous tutorials and I just can't seem to find out why it's still failing for me. Basically setting sendingViewController.delegate to self ends up being nil in sendingViewController. I understand this is very likely because the reference to the sendingViewController is being disposed of. But here is why I'm asking this again.
First, almost every tutorial and every other StackOverflow post is wiring up the mainViewController and the sendingViewController differently. I'm trying to make this work through a Navigation Controller, what one would think is the most common pattern for this.
In the app I'm building (which is more complex than the sample I'm going to show), the mainViewController calls the Settings viewController through a right navbar button. Then the user can select items from a list, which opens a controller with a searchBar and a tableView of items to select from. I need that third view controller to return the selected item from the table view to the settings screen. I'm using storyboards as well. I'm fairly new to Swift and I'm not ready to do all this "programmatically". Any way in the sending view controller, my delegate which should have been set in the calling view controller is nil and I can't invoke the protocol function in the main view controller to pass the data back.
I did a tutorial directly (not using Nav controllers) and I got that to work, but the moment I deviate away, it starts failing. I then put together a streamlined project with two view controllers: ViewController and SendingViewController. ViewController was embedded in a navigation controller and a right bar button was added to go to the SendingViewController. The SendingViewController has a single UI Button that attempts to call the protocol function and dismiss the SendingViewController. I'm not using Seque's, just a simple buttons and protocol/delegate pattern as I can.
My question is what am I missing to actually set the SendingViewController.delegate correctly?
Here's some code:
//ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var showDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func fetchDataButton(_ sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SendingViewController") as! SendingViewController
controller.delegate = self
print("fetching data")
present(controller, animated: true, completion: nil)
}
}
extension ViewController: SendingViewControllerDelegate {
func sendData(value: String) {
print("got Data \(value)")
self.showDataLabel.text = value
}
}
and
// SendingViewController.swift
import UIKit
protocol SendingViewControllerDelegate {
func sendData(value: String)
}
class SendingViewController: UIViewController {
var delegate: SendingViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.navigationController?.popViewController(animated: true)
}
}
Here is a screenshot of the Storyboard:
The ChildViewController does have a storyboard id name of "ChildViewController". All buttons and labels have their appropriate IBOutlet and IBAction's set up.
Help!
i copy paste your code .. its working perfect .. i make just one change
instead of pop you need to use dismiss as you are presenting from your base viewController
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.dismiss(animated: true, completion: nil)
}
here is the project link we.tl/t-NUxm9D26XN
I managed to get this working. In the receiving/parent view controller that needs the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as! sendingViewController
controller.cityDelegate = self
}
Then in the sending view controller in my tableView did select row function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let city = filtered[indexPath.row]
searchBar.resignFirstResponder()
self.navigationController?.popViewController(animated: true)
self.cityDelegate?.addCity(city)
self.dismiss(animated: true, completion: nil)
}
I don't think I should be both popping the view controller and dismissing it, but it works. Also in the view controller I did this:
private var presentingController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentingController = presentingViewController
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
}
}
I don't know if I really need this didMove() or not since it doesn't really do anything.
But some combination of all this got it working.
In my other app I'm not using a navigation bar controller and the standard delegate/protocol method works like a charm.

How can I change the state of a UISwitch so that it appears in the off position?

I am trying to have a switch appear in the off state once a view loads. Not always, but only if a boolean value that I created("switchBool") is false.
I've tried using the two ways on the apple documentation website. The two ways are shown in my code example. One is commented out.
import UIKit
class ViewController: UIViewController {
var switchBool = false
#IBAction func switchControl(_ sender: UISwitch) {
//sender.isOn = switchBool
sender.setOn(switchBool, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
The app is building and running without errors. However, I want the switch to be in the off state if "false" is assigned to the bool "switchBool", which in my example it is, but no matter what I try the switch always appears in the on state when the view loads up.
You need to create an IBOutlet for your switch(using the assistant editor button and storyboard). Then, you can just set the switch to be off in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
yourSwitch.setOn(switchBool, animated: true)
}
In the storyboard you can set it to be off in the Attribute inspector

Swift iOS -SplitViewController Won't Let Me Hide StatusBar?

I followed this tutorial to smoothly hide the statusBar smoothly hide statusBar and everything works fine when I use it on practice projects. I use the code in other project's that do not have SplitVC but have a tabBar and uses a navVC & tableView and everything works fine. In those I can successfully make it appear/disappear.
In my actual project I'm using a SplitViewController for iPad. I noticed when I implemented the directions from the link to my SplitViewController the statusBar wouldn't hide. I then made a new project using Apple's default MasterDetailApp to make sure I wasn't doing anything wrong but it doesn't work there either. I kept all of Apple's original code and only added in the necessary methods to make the statusBar appear/disappear
in info.plist I added the View controller-based status bar appearance and set it to YES
in storyboard I added a purple button to the DetailVC to trigger the statusBar disappearance. I also added in the method to make the backBar button disappear/reappear
I added all the methods to make the statusBar disappear/disappear to the DetailVC scene.
I added a tapGesture to the scene to make the statusBar and backButton reappear
I clicked the Plus button on the Master scene, a date appeared, clicked it to get to the DetailVC, pressed the purple buttonPressed to hide the statusBar and backButton but only the backButton gets hidden. I touch the background and the backButton reappears. The statusBar doesn't move.
I kept all the original code from Apple's project's and added mines below it:
class DetailViewController: UIViewController {
//MARK:- Apple's code
#IBOutlet weak var detailDescriptionLabel: UILabel!
func configureView() {
if let detail = detailItem {
if let label = detailDescriptionLabel {
label.text = detail.description
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
configureView()
// make backButton and statusBar reappear when scene is tapped
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(showBackButtonAndStatusBar))
view.addGestureRecognizer(tapGesture)
}
var detailItem: NSDate? {
didSet {
configureView()
}
}
//MARK:- Outside of the tapGesture in viewDidLoad everything below here is what I added
// bool to determine wether to hide the statusBar or not
var statusBarShouldBeHidden = false
// api method to allow the staus bar to be hidden
override var prefersStatusBarHidden: Bool{
return statusBarShouldBeHidden
}
// api method to animate status bar appearance/disappearance
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{
return .slide
}
#IBAction func buttonTapped(_ sender: UIButton) {
// 1. hide backBar button
navigationItem.setHidesBackButton(true, animated: false)
// 2. set bool to true
statusBarShouldBeHidden = true
UIView.animate(withDuration: 0.25){
// 3. api method to allow the statusBar to disappear
self.setNeedsStatusBarAppearanceUpdate()
}
}
//called when background is touched and added to tapGesture in viewDidLoad
#objc func showBackButtonAndStatusBar(){
// 1. set bool to false
statusBarShouldBeHidden = false
UIView.animate(withDuration: 0.25){
// 2. bring statusBar back
self.setNeedsStatusBarAppearanceUpdate()
}
// 3. bring backButton back
navigationItem.setHidesBackButton(false, animated: true)
}
}
How can I get the SplitViewVC to let me hide the statusBar?
It appears that you are trying to hide the status bar through the detail view controller. The status bar in the user interface is controlled only by the split view controller because it is on top of the view controller hierarchy. Therefore, the easiest way to control the behavior of the status bar is to subclass UISplitViewController and then override the prefersStatusBarHidden computed property in the subclass. Also, make sure you go to your storyboard and change the split view controller's custom class field in the Identity inspector to your subclass.
---Updated Answer---
#LanceSamaria Okay, I took your code above and tweaked some things. First of all, I only added the button action and not the tap gesture. Also, I commented out the hiding the back button, because this is important in the UI in order to be able to go back to the master view. Anyway, now when you click the button, the SplitViewController will hide the status bar. If you click the button again, then the status bar will reappear.
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var detailDescriptionLabel: UILabel!
var statusBarShouldBeHidden = false
func configureView() {
// Update the user interface for the detail item.
if let detail = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.description
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.configureView()
}
/* override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{
return .slide
} */
var detailItem: NSDate? {
didSet {
// Update the view.
self.configureView()
}
}
#IBAction func buttonTapped(_ sender: UIButton) {
// 1. hide backBar button
//navigationItem.setHidesBackButton(true, animated: false)
// 2. set bool to true
statusBarShouldBeHidden = !statusBarShouldBeHidden
UIView.animate(withDuration: 0.25){
// 3. api method to allow the statusBar to disappear
guard let svc = self.splitViewController as? SplitViewController else { return }
svc.statusBarShouldBeHidden = self.statusBarShouldBeHidden
svc.setNeedsStatusBarAppearanceUpdate()
}
}
}
Also, one more really important thing. Below is my code for the split view controller subclass. Note that I use the same variable name "statusBarShouldBeHidden" in both the split view controller and the detail controller.
import UIKit
class SplitViewController: UISplitViewController {
var statusBarShouldBeHidden = false
override func viewDidLoad() {
super.viewDidLoad()
}
override var prefersStatusBarHidden: Bool {
return statusBarShouldBeHidden
}
}
Thank you for posting this question. This has helped my learn a lot trying to solve this problem. Please let me know if you still have a question about what this.

How do I make navigation bar to show in root viewcontroller A the second time it's presented? (A>B>A)

I'm developing a shopping list like app where I have a Navigation Controller and the root view controller is the screen where the user can search for products (SearchViewController). When the user selects a product it segues to DetailViewController. This view has an option to check out or add more products. If users click on "Add more products" I have to segue to SearchViewController so they can search for more products. I want to present this VC again but I want the Nav Bar to show this time since I want to be able to go back if I decide not to add any other products.
Right now I'm sending the shoppingContext in the segue to determine from the SearchVC if I come from "DetailsVC" or not.
I think there's a problem with the way I'm adding view controllers to the navigation stack, but I've never encountered a problem like this and don't know what else to try.
With my current implementation (performSegue from DetailsVC to SearchVC) any time I click on a new item it segues twice to the Details screen, which I suspect may also be caused by the same navigation stack issue.
I tried creating a new object of SearchVC and pushing it to the stack instead of performing the segue but it didn't work either.
What can I do to fix it?
Basically, in detailsVC I do the following:
let segueAction = SegueAction(name: "segueToSearch", preparer: {
destinationVC in
if
let activeVC = destinationVC as? SearchViewController
{
activeVC.shoppingList = self.shoppingViewModel.shoppingList
}
})
performSegue(withIdentifier: segueAction.name, sender: segueAction)
The segue "segueToSearch" is a Show (push) type segue.
Then in the SearchVC I check if shoppingList != nil and if so do:
navigationController?.setNavigationBarHidden(false, animated: false)
If I check if the navigation bar is hidden it returns false but I still don't see it.
Hi it's pretty straight forward. Answer can be found here:
Navigation bar show/hide
[[self navigationController] setNavigationBarHidden:NO animated:YES];
And I would put a property to check in the viewWillApear.
-- EDIT: --
TESTED: I added it to a button action, works also in the viewDidAppear when dismiss back from to detail.
Hope it helps.
class ViewController: UIViewController {
var didHideNav: Bool = false
#IBAction func changeHidden(_ sender: UIButton) {
if !didHideNav {
print("Should Be Hidden")
self.navigationController?.setNavigationBarHidden(true, animated: true)
didHideNav = true
}else{
print("Should Be Visible")
self.navigationController?.setNavigationBarHidden(false, animated: true)
didHideNav = false
}
}
override func viewDidAppear(_ animated: Bool) {
if !didHideNav {
print("Should Be Hidden")
self.navigationController?.setNavigationBarHidden(true, animated: true)
didHideNav = true
}else{
print("Should Be Visible")
self.navigationController?.setNavigationBarHidden(false, animated: true)
didHideNav = false
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

How do I set the default tab for tab bar controller in swift

I'm new to Swift, and about a 5 out of 10 on the Objective-C knowledge scale..
I created a basic three tab Swift application. Each tab has an associated swift class file, e.g. FirstViewController.swift , SecondViewController.swift, ThirdViewController.swift.
When I select the third tab, I now open the application preference settings using a viewDidAppear function override in ThirdViewController.swift, e.g.:
override func viewDidAppear(animated: Bool) {
// open app preference s
if let url = NSURL(string:UIApplicationOpenSettingsURLString) {
UIApplication.sharedApplication().openURL(url)
}
}
Though, prior to opening the preferences, I would like to set the active tab back to the first tab. How is this done elegantly in Swift.
The following does not work:
self.tabBarController.selectedIndex = 0
As the UIViewController of the ThirdViewController class does not have a tabBarController.
Brian.
User Omkar responded above with the correct answer. I can successfully switch the first tab using the following viewDidAppear in ThirdViewController.swft
override func viewDidAppear(animated: Bool) {
self.tabBarController?.selectedIndex = 0
}
I followed #Paolo Sangregorio way The code to do it as follows
in appdelegate find applicationDidBecomeActive function and add this lines
let tabBarController = self.window?.rootViewController as! UITabBarController
tabBarController.selectedIndex = 0
//Use viewWillAppear (instead of) viewDidAppear to prevent a screen flicker
var freshLaunch = true
override func viewWillAppear(animated: Bool) {
if freshLaunch == true {
freshLaunch = false
self.tabBarController.selectedIndex = 2
}
}
If it is a tab bar application you should have the tabbarcontroller variable in your appdelegate.
You can set the current tab by setting the selectedIndex on that variable, from the appdelegate.
In case you are using Tab bar item
Swift 3
#IBOutlet weak var Tabbar: UITabBar!
override func viewDidLoad() {
.........
tabBar.selectedItem = tabBar.items![newIndex]
}
If you want him to come without delay. Yo can use in viewDidLoad()
override open func viewDidLoad() {
super.viewDidLoad()
// Set Default tab
self.selectedIndex = 1
}

Resources