Passing the Boolean Flag between the views in swift - ios

I have a single view controller containing the some text labels and multiple other properties. I want to use that view controller for both editing the view and viewing the contents of view. Now what I want to do is pass the Flag so that it indicates whether the request is for editing the fields or viewing the fields.
I have done this but did not work. Lets say my view controller containing the view is third View Controller and I am accessing this view controller from first and second view controller.
//In third View COntroller
var isEdit: Bool! = false
func viewDidLoad(){
self.loadData()
}
override func loadData{
if isEdit == false{
//print this is edit mode
}
else if isEdit == true{
//This is view mode
}
else{
//print error navigtion
}
}
and I am accessing to this view controller from first view controller on button click action as
let mapViewFirst = self.storyboard?.instantiateViewControllerWithIdentifier("ThirdViewController") as? ThirdViewController
mapViewFirst.isEdit == true
self.navigationController?.pushViewController(mapViewFirst!, animated: true)
and from secondViewController as
let mapViewSecond = self.storyboard?.instantiateViewControllerWithIdentifier("ThirdViewController") as? ThirdViewController
mapViewSecond.isEdit == false
self.navigationController?.pushViewController(mapViewSecond!, animated: true)
it always runs on isEdit == false
i.e the view controller is always on Edit mode it never moves to else condition. Can anyone find the better solution to my issue.

Assuming the difference between isEdit and iseditMode is a typo in your question and not in your actual code, and also that the difference between mapViewFirst and mapViewControllerObj and mapViewSecond and mapViewControllerObj are also just typos in your question and not your actual code.
Then it is not working because viewDidLoad() is called when the view controller is loaded into memory, which is occurring when instantiateViewControllerWithIdentifier is called.
To get the functionality you want move loadData() from viewDidLoad to viewWillAppear which will get called when the view controller is pushed to the stack. (and make sure you override viewWillAppear as you are supposed to, not like you have omitted from viewDidLoad())

Related

Passing variables back from a ViewController to a previous one, but variables not updating?

I have two view controllers which I am interested in passing variables from one view controller to the next in a backwards manner. To achieve this I used a protocol, however, the variable in the first view controller are not updating when going back from view controller two to view controller one:
Below is my code for the first view controller:
import UIKit
class BlueBookUniversalBeamsVC: UIViewController {
var lastSelectedTableRowByTheUser: Int = 0
var lastSelectedTableSectionByTheUser: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
print(lastSelectedTableRowByTheUser)
print(lastSelectedTableSectionByTheUser)
}
}
extension BlueBookUniversalBeamsVC: ProtocolToPassDataBackwardsFromDataSummaryVcToPreviousVc {
func dataToBePassedUsingProtocol(passedSelectedTableSectionNumberFromPreviousVc: Int, passedSelectedTableRowNumberFromPreviousVc: Int) {
self.lastSelectedTableRowByTheUser = passedSelectedTableRowNumberFromPreviousVc
self.lastSelectedTableSectionByTheUser = passedSelectedTableSectionNumberFromPreviousVc
print("Last selected row passed back from SummaryVC is equal to \(passedSelectedTableRowNumberFromPreviousVc)")
print("Last selected section passed back from SummaryVC is equal to \(passedSelectedTableSectionNumberFromPreviousVc)")
}
Below is my code inside the second view controllerL
import UIKit
class BlueBookUniversalBeamDataSummaryVC: UIViewController {
var delegate: ProtocolToPassDataBackwardsFromDataSummaryVcToPreviousVc?
#objc func navigationBarLeftButtonPressed(sender : UIButton) {
let main = UIStoryboard(name: "Main", bundle: nil)
let previousViewControllerToGoTo = main.instantiateViewController(withIdentifier: "BlueBookUniversalBeamsVC")
if delegate != nil {
delegate?.dataToBePassedUsingProtocol(passedSelectedTableSectionNumberFromPreviousVc: self.selectedTableSectionNumberFromPreviousViewController, passedSelectedTableRowNumberFromPreviousVc: self.selectedTableRowNumberFromPreviousViewController)
}
self.present(previousViewControllerToGoTo, animated: true, completion: nil)
}
}
The weird thing is that in Xcode console when I go back from VC2 to VC1, inside the protocol function extension in VC1 I can see the values being printed correctly. However, when the values get printed from inside viewDidLoad() both of them are showing as 0. Any idea why this is happening, is there something I am missing out here?
Your second view controller is instantiating a new instance of the first view controller rather than using the instance that was already there. The second view controller shouldn’t present the first view controller again, but rather dismiss (or pop) back to it, depending upon the first presented or pushed to it.
By the way, the delegate property of the second view controller that points back to the first one should be a weak property. You never want a child object maintaining a strong reference to a parent object. Besides, delegates are almost always weak...

A newly-presented view controller immediately gets dismissed after appearing?

I have a view controller that calls another view controller with present(newViewController.... However, whenever the function that calls this present function is called, the new view controller pops up on the screen for a second before being dismissed again. There are no functions in this new view controller to dismiss itself.
The only thing I can think of that may be causing this is my use of SnapKit and overriding of viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let data = data {
return
} else { setupAVFoundation() }
}
data is an optional value that is set by the time I present the new view controller, so viewDidLayoutSubviews returns at the if let statement. However I have confirmed that upon presenting the new view controller, viewDidLayoutSubviews gets called like four times. I can't figure out why this is happening or why it would interrupt presentation of a new view controller.
Here is where the new view is presented:
func found(code: String) {
print(code)
data = code
present(ItemSelectViewController(), animated: true, completion: nil)
}

How to pass data from First ViewController directly to the Third View Controller using segue?

I was able to perform the task of passing value from one view controller to another view controller. I had a textField and submit button in one view controller and in another I had a label, after I gave input to the textField, I was able to replace the label text with textField's text.
But now what I am trying is that when I click on submit button in first view controller , it should move to second view controller (performSegue) and at the same time it should pass the value to the third view controller..
But I am getting an error if I do that, is there a simple way to pass values from one view controller to third view controller where I can skip "n" number of view controllers which come in between the target and destination?
Here's an example:
Suppose there are three viewControllers, first viewController has a
textField and a submit button, second view Controller has a "NEXT"
button and third View Controller has a label.
When I enter the textField and click on submit , it should submit the textField.text to third View Controller and should perform segue to second view controller,
now when I am on second view controller it'll show NEXT button. After I click on next, it'll take me to third View controller where it'll show me the first View COntroller's textField's text in the label...
My question is, how to do that? I went through a lot of youtube videos of implementing segues but nobody talked about how to pass data from one view controller to another view controller by skipping some view controllers.
I hope you do understand my question.
Your time and help will be highly appreciated!
Thank You
assign storybordID to your controller for ex here i have assigned "HomeVC" (it would be greate if you assign class name and storybordid same)
let objthirdController = self.storyboard?.instantiateViewController(withIdentifier: "thirdControllerStoerybordID") as! thirdControllerclassname
objthirdController.variable = self.variable
self.navigationController?.pushViewController(objthirdController, animated: true)
The way you are able to perform the task of passing value from one view controller to another view controller.
Repeat the same when you are in next controller, till you reach your destination view controller.
you should use Notification Observer Design Pattern but its better to pass it throw the view controller instead of passing directly to destination ViewController but here is the solution:
struct NotificationKeys {
static let myKey = "myKey"
}
class SourceViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func yourButtonPressed(_ sender: UIButton) {
let name = Notification.Name(rawValue: NotificationKeys.myKey)
NotificationCenter.default.post(name: name, object: nil)
performSegue(withIdentifier: "identifier", sender: self)
}
}
you can pass with object too, its so simple with object too,
just search pass data with NotificationCenter in google if you don't understand the code ;)
class DestinationViewController: UIViewController {
deinit {
NotificationCenter.default.removeObserver(self)
}
#IBOutlet weak var yourLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
createObserver()
}
func createObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(updateLabel(notification:)), name: Notification.Name(rawValue: NotificationKeys.myKey), object: nil)
}
#objc func updateLabel(notification:NSNotification) {
let isText = notification.name.rawValue == NotificationKeys.myKey
let text = isText ? "\(isText)" : ""
yourLabel?.text = text
}
}

Accessing UISegmentedControl #IBOoutlet from another class

I have Table View Controller, intended to serve as a settings page, that contains a UISegmentedControl with 3 segments:
class SettingsView: UITableViewController {
#IBOutlet weak var ButtonSelection: UISegmentedControl!
//Index 0 is default selection (first)
override func viewDidLoad() {
super.viewDidLoad()
// some code
}
Separately, I have a Navigation View Controller that controls 3 different UIViewControllers with corresponding Storyboard IDs ("first", "second", and "third").
I'm trying to prepare the Navigation View Controller to present the appropriate View Controller based on UISegmentedControl selection. However, I keep getting "fatal error: unexpectedly found nil while unwrapping an Optional value" and not sure how to resolve.
This is what I have tried:
class NavViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if SettingsView().ButtonSelection.selectedSegmentIndex == 0 {
print ("first segment is selected")
let destinationController = storyboard?.instantiateViewController(withIdentifier: "first")
else if SettingsView().ButtonSelection.selectedSegmentIndex == 1 {
print ("second segment is selected")
let destinationController = storyboard?.instantiateViewController(withIdentifier: "second")
else if SettingsView().ButtonSelection.selectedSegmentIndex == 0 {
print ("third segment is selected")
let destinationController = storyboard?.instantiateViewController(withIdentifier: "third")
Could anyone point me in the right direction, please? Thanks in advance!
You have a couple of problems.
Problem 1: A UITableViewController is not set up to host anything but a table view. It's content view is locked to be a table view. If you want a table view that's managed by a UITableViewController and you want other content in the view controller, you need to make the UITableViewController a child of another view controller. The good news is that that is trivially easy using a container view and an embed segue.
Problem 2 is a "don't do that" problem. You should treat a view controller's views as private. Another view controller should not try to look at or change another view controller's segmented control. (It's bad design, and it also can lead to crashes like the one you describe because you can't be sure if the other view controller's views have been loaded yet.) Instead, you should add an integer property "selectedIndex" to the view controller that contains the segmented control, and use that to read/write the selected segment. (In OOP terms, you add a public interface to your view controller's "contract" that exposes the features you want to expose, and then add code that provides that interface.)

Showing/Hiding primary view controller of UISplitViewController shifts detail view

I have a UISplitViewController for Master/Detail functionality on an iPad. The master view controller shows a list of items, and when the user selects one the detail information is shown in the detail view controller. I have the detail view controller set to navigate to another view controller to display a graph. When this happens, I hide the primary view controller with the following lines in my prepareForSeque.
if let svc = self.splitViewController {
svc.preferredDisplayMode = .PrimaryHidden
}
This works great. When navigating back to the detail view from the graph view I would like to again show the primary view from the split view controller. I put this in viewWillAppear.
guard let svc = self.splitViewController else { return }
if svc.preferredDisplayMode != .Automatic {
svc.preferredDisplayMode = .Automatic
}
Again this works exactly as I would expect. The problem is the detail view changes size during this process and it is not laid out properly when returning from the graph view.
Here is a screen shot before navigating to the graph view, and before hiding the primary view of the UISplitViewController.
And this is after returning from the graph view.
My attempt to fix the issue was to force the detail view to layout itself. I tried the following:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
guard let svc = self.splitViewController else { return }
let detailNavController = svc.viewControllers[svc.viewControllers.count-1] as! UINavigationController
detailNavController.view.setNeedsLayout()
}
It sort of works but causes an ugly jump in the interface as the detail view appears and then a second later is relaid out. Is there a better way to get the view laid out properly before it is displayed?
I got it working, and figured I would share in case anybody else runs into the same issue. As Tim stated in the comments, it is a matter of running layout early enough in the chain to get things laid out before the view is presented on screen.
I did the following in the Graph view controller scene:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
guard let svc = self.splitViewController else { return }
svc.preferredDisplayMode = .Automatic
}
This set the split view back to showing both the primary and secondary view controllers very early in the process.
The place where I was going wrong was the parent of my detail view controller is a UINavigationController. I assumed that since this was being sized wrong, I needed to get it to lay itself out again after setting the split view controller to show the primary and secondary views. This was a wrong assumption. What ended up working was going up one more level to the UISplitviewController and having that perform a layout on itself.
In my detail view controller I did the following:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
guard let svc = self.splitViewController else { return }
svc.view.setNeedsLayout()
svc.view.layoutIfNeeded()
}
This was early enough in the chain that all the layout gets completed before the view is shown, but late enough that the split view controller has the appropriate sizing information for showing both the primary and secondary views.
Hopefully this helps someone else, and thanks for the comments.

Resources