How do I change a segue based on a UISwitch? - ios

I'm trying to make a settings tab that changes what view the user sees in a different tab based on a UISwitch. How would I make it so that when the switch is on then the user changes to that tab, it goes to ViewController1 and when the switch is off and they change tabs, it goes to ViewController2? I've attached a screenshot that might explain it a bit better. I censored the information that would give away my app's purpose so as to not attract copycats and whatnot.
As you can see, my main problem with presenting modally full screen is that the TabBarController is eliminated, leaving users with no way of turning off the setting or looking at another tab. I don't want to present it as a popover tab or push it, because I want it to feel like nothing ever happened.
The larger view will appear modally when the switch is on and viewWillAppear is called on ViewController1. Here's my code for that:
SettingsView.swift
import UIKit
class SettingsTableViewController: UITableViewController {
#IBOutlet weak var viewSwitch: UISwitch!
override func viewDidLoad() {
tableView.allowsSelection = false
super.viewDidLoad()
}
#IBAction func switchIsChanged(_ sender: Any) {
if viewSwitch.isOn == true{
TableViewController.switchState = true
}
if viewSwitch.isOn == false{
TableViewController.switchState = false
}
}
}
I've moved the code from TabBar.swift to TableView.swift because the solution I tried doesn't really regard the TabBarController. Here's TableView.swift:
import UIKit
class TableViewController: UITableViewController {
public static var switchState: Bool?
override func viewWillAppear(_ animated: Bool) {
if TableViewController.switchState == true {
performSegue(withIdentifier: "LargeViewSegue", sender: self)
}
else {
print("Showing standard view.")
}
}
}
This code works, but as I said before, it shows no TabBar, which is essential to my app. The ideal solution is to change the relationship segue from the TabBarController to the LargeView/TableView. Unfortunately, from what I've heard, You can't create custom relationship segues programatically. If someone could prove me wrong, that would be great.
Thanks in advance.

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

Viewcontroller loaded twice

I am trying create Tab Bar application. I added to the first viewcontroller a button and a text field. When I wrote some text and pushed button I expected that loads second viewcontroller and in the label field appears text from the text field.
I have 2 problems.
Second viewcontroller loaded twice.
when I push button on tab bar and load first viewcontroller again? me text disappear.
I only start work with Xcode. Help me please and describe resolve in details🙏
import UIKit
var tfTextString: String = ""
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBAction func enter(_ sender: Any) {
if textField.text != "" {
//performSegue(withIdentifier: "segue", sender: self)
//tfTextString = textField.text!
self.tabBarController?.selectedIndex = 0
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var secondController = segue.destination as! SecondViewController
secondController.myString = textField.text!
}
}
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var myString = String()
override func viewDidLoad() {
super.viewDidLoad()
label.text = myString
}
}
It looks like your view controller is loaded twice because you are loading it twice.
Don't think of each screen on the storyboard as being it's own distinct entity as per the singleton pattern. They aren't. They're their own distinct class / view combination, which gets instantiated (loaded) once every time you call it. To put it another way, you aren't looking at the 'actual' classes in the storyboard, you're looking at the template that's used to build the class when called for. So you're calling for it twice, in two different places, and it happily produces multiple copies of the underlying view / controller for you.
I think you don't want to use a navigation controller to 'push' the view back onto the stack. You're going to want to tell the tab bar to switch to a different view instead.
The swift version of the code you're looking for is:
self.tabBarController?.selectedIndex = 1
I think the objective-C equivalent would have been...
[[self tabBarController] setSelectedIndex:1];
But it's been long enough since I've done swift I may have overlooked something important in there.

Swift Hide Button In Another View Controller

Currently working on my first IOS application. I have a purchase button, on success this currently sets a test button on the same view controller to hidden. Code is as follows
Decleration
#IBOutlet weak var Test: UIButton!
hide button on successful purchase
Test.isHidden = true
Now this works on my Test button, which is sat in the PurchaseViewController,class is the MasterViewController.Swift. (Purchase button that initiates this method is also in the same view controller)
PlanViewController also has a button, and class is also linked to MasterViewController.Swift. This has a separate button that i wish to hide on success of the purchase button.
When I utilise the same code as above for the button, it crashes, is their a limitation on manipulating other view controllers while you are not in it? I would have thought this worked given that they both have the Masterviewcontroller.swift as the class
Thanks
Although sometimes possible, it's generally not a good idea to directly manipulate one view controller's view from another view controller, as you are trying to do. Here is how I would do what you are trying to do.
First, set a segue identifier between your two view controllers by clicking on the segue in the storyboard and going to the attributes inspector. I suggest goToMasterViewController
In both MasterViewController.swift and PurchaseViewController.swift declare a variable var buttonHidden = false
In PurchaseViewController.swift add the following code, which will be called just before your segue to MasterViewController is performed:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "goToMasterViewController") {
let destinationController = segue.destination as! MasterViewController
destinationController.buttonHidden = buttonHidden
}
}
When you hide the button in PurchaseViewController, also set buttonHidden = true
And finally in MasterViewController.swift:
override func viewWillAppear(_ animated: Bool) {
testButton.isHidden = buttonHidden
}

Remove "Back" text from navigation controller [duplicate]

This question already has answers here:
Removing the title text of an iOS UIBarButtonItem
(39 answers)
Closed 7 years ago.
I do not want to see the "Back" text that is displayed automatically when going to a view controller with no Title. I would like this to happen everywhere on my application, is there a few lines of code I can put in the app delegate to make this possible?
I have tried a few approaches from SO before posting this question and have found no success. I want to this in Swift, not Obj-c.
I tried this with no success; it ran fine, but the back text was still displayed in the next view controller.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let backItem = UIBarButtonItem()
backItem.title = "Something Else"
navigationItem.backBarButtonItem = backItem // This will show in the next view controller being pushed
}
I would like to put this in app delegate so it would happen throughout my application rather than having to put that into every single swift file in my project. Anybody have any ideas?
There are several different ways to do this. I’ll present a solution that gives you a bit more flexability as you’ll want to display a button some times, hide other times, and customize at other times. This example assumes you are using Navigation Controller based UI, but can be adapted to other types.
Whenever I create an app I like to create my own UIViewController class and have all of my UIViewControllers inherit from this single view controller. If I want to customize something for all my UIViewControllers I can do this at my new super class level since all the other views inherit from that.
In my sample code below I create my custom UIViewController called MasterViewController. I have all of my UIViewControllers inherit from it: ViewController, ViewController2 & ViewController3. Read the notes in the code below to understand what’s going on.
NOTE: Make sure you check for the case where the UIViewController should not have the back button as this would be the case where there is nothing to return to. You can add this code to MasterViewController but I did not for this sample.
//=========================================================
//MasterViewController
//=========================================================
// Master View Controller that all UIViewControllers inherit from.
import UIKit
class MasterViewController: UIViewController {
// This will be title if the user doesn't change it in a subclass.
var backButtonTitle = "Default Back Title"
override func viewDidLoad() {
// This resets the default back button to always be shown unless the user calls createCustomBackButtonWithTitle from child class.
self.navigationItem.hidesBackButton = false
super.viewDidLoad()
}
func createCustomBackButtonWithTitle(customTitle: String) {
// Hide the default back button.
self.navigationItem.hidesBackButton = true
// Programmatically create custom back button.
let backButton = UIBarButtonItem(title: customTitle, style: UIBarButtonItemStyle.Plain, target: self, action: "goBack:")
self.navigationItem.leftBarButtonItem = backButton
}
#IBAction func goBack(sender: UIButton!) {
navigationController?.popViewControllerAnimated(true)
}
}
//=========================================================
//ViewController
//=========================================================
// This view controller customizes the back name and inherits the default "go back" behavior from MasterViewController.
import UIKit
class ViewController: MasterViewController {
override func viewDidLoad() {
super.viewDidLoad()
createCustomBackButtonWithTitle("ButtonName")
}
}
//=========================================================
//ViewController2
//=========================================================
// This view controller customizes the back name but overrides the default "goBack" behavior from MasterViewController to do something different
import UIKit
class ViewController2: MasterViewController {
override func viewDidLoad() {
super.viewDidLoad()
createCustomBackButtonWithTitle("ButtonName 2")
}
// Override the back button behavior from super class because we don't want default "back" behavior.
#IBAction override func goBack(sender: UIButton!) {
// Costume Code here
}
}
//=========================================================
//ViewController3
//=========================================================
// This view controller inherits from MasterViewController, but gets the default "back" behavior from a normal
// UINavigationController since it never calls createCustomBackButtonWithTitle to change the behavior
import UIKit
class ViewController3: MasterViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
Most people wind up doing something like #xdeleon suggested, but I highly recommend not doing that, if for no other reason that that you'll break the built-in swipe behavior to navigate backwards in the view controller stack. There are a couple answers on SO that tell you how to restore this back-swipe functionality, but as someone who's had to fix this in an existing app with 200+ view controllers, I'd like to save you a lot of pain.
If you don't mind working in storyboards, then make sure that each of your view controllers' navigation item's back button text is an empty string. However, since that's a lot to keep track of, it's marginally easier to do in code, and the only way I've found works perfectly is to create a cachedTitle instance variable on your view controller, and implement the view controller's viewWillAppear() and viewWillDisappear() methods like so:
override func viewWillAppear(animated: Bool) {
// Reset the view controller's title only if it doesn't already have one.
if (navigationItem.title == nil || navigationItem.title == "") && navigationItem.titleView == nil {
navigationItem.title = cachedTitle
}
}
override func viewWillDisappear(animated: Bool) {
if navigationItem.title != nil { // not 'if let'!
cachedTitle = navigationItem.title
navigationItem.title == ""
}
}
You can either put these methods in a UIViewController subclass that all of your view controllers extend, or, better yet, put these in custom methods in a category on UIViewController, and just call them in your view controllers' viewWillAppear() and viewWillDisappear() calls.
If you want to use something other than the back button's '<' symbol, don't add a custom back or left button; just set a custom back indicator image and mask on the navigation bar.

Resources