I have view1 and view2. In view2 I set up a delegate and enter some data. When I press the save button i view2 I am sent to view1. In view1 I get the data I entered in view2. BUT... when I try to update a label or button text in view1 with the data from view2 nothing happens. View1 is not updated.
This is the delegate function in view1 which should update the label:
func getCurrentContact(ViewController: ViewControllerSettings, data: (ContactDetails)) {
NSLog("Yes, the delegate is called")
self.lblContact1.text = data.name1 as? String
}
Anyone know why lblContact1.text is not updated? It just stay the same as on startup. The NSLog of data.name1 contains the correct data I entered in view2. Seems like view1 needs to be refreshed or something...
BTW; Does anyone know about a working swift project in GitHub or something that has navigation controller and a working delegate for sending data from view2 to view1? Would be outstanding...
Try rewriting your code like this to have some more success debugging this:
func getCurrentContact(ViewController: ViewControllerSettings, data: (ContactDetails)) {
println("Yes, the delegate is called")
if let name = data.name1 as ? String {
println("Setting lblContact1 with \(name)")
self.lblContact1.text = name
} else {
println("Either data was empty or data.name1 couldn't be extracted")
}
}
If you're actually receiving data.name1 as expected – then I would check that the the code you supplied is actually running on the main thread.
Note that I've also replaced NSLog with println for a number of reasons
Related
I got stuck because one ViewController trying to pass data from one ViewController to another. To explain my code is that I have label in mainViewController named name1 with data inside of it and I am trying to pass that data that is in text format to the name2 label in the firstViewController of name2 label, Can you please help me with it.
Thank you in advance
class mainViewController: UIViewController {
//its inside a segment controller
#objc func Seg(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
let firstViewController = SharedViewController()
print(self.name1.text!)
firstViewController.name2.text = self.name1.text!
self.addChild(firstViewController)
self.bottomContainer.addSubview(firstViewController.view)
firstViewController.didMove(toParent: self)
default:
let secondViewController = Shared2ViewController()
self.addChild(secondViewController)
self.bottomContainer.addSubview(secondViewController.view)
secondViewController.didMove(toParent: self)
}
Don't try to manipulate another view controller's views directly. That violates the principle of encapsulation and often doesn't work (as in this case.)
You should add a string property to the other view controller, and install that string into the label text in viewWillAppear.
Also note that creating a view controller with an init like SharedViewController() doesn't usually do what you want. (You won't get the view controller's views loaded from it's storyboard/xib.) You usually want to use the Storyboard instantiate method, or to load it from the XIB.
I have a UIViewController with several UITextFields. When tap one text field, it should present the barcode scanning view controller. Once the scanning is completed, my barcode scanning viewcontroller is disappearing (used "dismissViewcontroller") and the scanned value should entered into the text field I tapped. This is working fine. I have set the delegate for each text field like this.
[field addTarget:metrixUIViewControllerIn action:#selector(executeScriptOnTextFieldChange:) forControlEvents:UIControlEventEditingChanged];
The problem is this :
Lets say I have set an alert to display inside this executeScriptOnTextFieldChange method. Once I tapped on the 1st text field, then the barcode scanner comes. Once I scanned barcode scanner closes and set the value for the first text field and fire the alert.Thats ok. But then if scanned by tapping the 2nd textfield and the string will set to that textfield and fire the alert related to 2nd textfield also fire the alert related to first textfield as well. I want to stop happening this. Is there any way to disable the delegate for one textfield? This happens because I am refreshing the view in the viewDidAppear. But I have to do that as well. Please help me.
UIControlEventEditingChanged for a textField can fire at many different events that are not even directly related to that textField, but related inderectly.
For instance, when your ViewController is presenting the barcodeScanner it may trigger a "resignFirstResponder" event on the textField. Also when the 2nd textField is tapped, cause the 2nd becomes first responder and the 1st suffers a "resignFirstResponder".
I suggest trying to use a UITapGestureRecognizer in your textField instead. Example:
Swift 4
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.textField.tag = 1
self.textField.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(fireTextField(_:))))
}
#objc func fireTextField(_ sender: UIGestureRecognizer){
let view = sender.view
guard view != nil else{
//Do nothing
return
}
let condition = view!.tag == 1
if condition{
//or do whatever other stuff you need
self.textField.becomeFirstResponder()
}else{
//Whatever for other textFields
}
}
This way, you could use the "tag" attribute to determine which textField is firing and so adjust "condition". You could also filter the flow with a switch using the "tag".
Not sure if any of this will really help as I would need more info about the flow you need to accomplish. Hope it does help!
What I have
I have a view controller (SelectNameViewController) with a UITableView on with a list of names. When the user clicks a name, it goes to a ViewController (ResultController) which has a container view that loads 3 ViewControllers (vc1,vc2,vc3) allowing the user to scroll through.
On ResultController I have an alamofire request that connects to a JSON file and downloads information based on the name selected:
Alamofire.request(.GET, sendyURLSet, parameters: ["name": passedName, "api_key": api_key])
.responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
self.age = json["age"].stringValue
self.userInfo.setValue(self.age, forKey: "age")
}
case .Failure(let error):
print(error)
}
}
On VC1 I have this in my viewDidAppear
if let a = userInfo.stringForKey("age") {
print("Age: \(a)")
} else {
print("nope")
}
Problem
I am having trouble loading data on a PageViewController child view after the content has been loaded from an Alamofire request.
When the user clicks a name and is taken to the ResultController the 3 views load into the container view. The first screen to appear is vc1.
However, in the console I just get:
Nope
If I swipe to vc2 and then back to vc1 I get:
Age 32
in the console.
Question
Can someone tell me why the value is not showing up on the first presentation of the ViewController (vc1) and tell me how to fix it?
It is driving me crazy
looks like vc1 is being loaded quicker than the data fetch is completing.
Either wait for the data to be loaded before you load the new controllers, or set the label directly when the data has completed
Likely, the callback to your Alamofire.request is called after viewDidAppear, and that explains why you do not see the value you set in the callback.
What you should want is that your code
if let a = userInfo.stringForKey("age") {
print("Age: \(a)")
} else {
print("nope")
}
is called after the callback is executed.
You have several means to this:
resultsController calls into VC1 to make it update its UI;
load resultsController from the callback (actually, you make sure you call its view method for the first time in the callback, i.e., when you add it as a subview to the parent);
or, you use KVO to make VC1 observe any changes to userInfo.age. This approach requires customising NSUserDefaults by way of deriving a new class from it.
Try following one. I think it may help you.
On completion of your network call (in completion handler block) set values to UI elements like label, using outlets. And call - (void)setNeedsLayout method.This method to indicate that the layout of a layer’s sublayers has changed and must be updated. The system typically calls this method automatically when the layer’s bounds change.
In my application, I have a mainViewController with some content on it. At some points, I load an overlay view controller from storyboard. The overlay view controller is smaller than the screen and is presented on top of the mainViewController. I initialize it the following way:
class MyOverlayViewController {
#IBOutlet var textLabel: UILabel!
#IBOutlet var countLabel: UILabel!
static let storyboard = UIStoryboard(name: "...", bundle: nil)
// Return a new view controller
class func newViewControllerWithData(data: AnyObject) -> UIViewController {
let vc = storyboard.instantiateViewControllerWithIdentifier("MyOverlayViewController") as! MyOverlayViewController
Timing.performAfterDelay(0) {
vc.titleLabel.text = data[...] // Load title label text
vc.countLabel.text = data[...] // Load count label text
}
return vc
}
}
I cannot set the text of the labels immediately in the method newViewControllerWithData, because that produces the following error: fatal error: unexpectedly found nil while unwrapping an Optional value. So the labels are nil when accessing them immediately in this method.
It seems like the two label outlets are not loaded immediately when the view controller is instantiated from storyboard, because this takes a (very) short time.
Therefore, I use my method Timing.performAfterDelay(0) which executes the code after the next run-loop cycle (it starts a timer with duration 0 and executes the code as callback). The code is (I have checked that) executed on the main thread.
The problem is the following:
Sometimes (not always, and not reproducible!), when loading the overlay view controller, for a fraction of a second the labels are empty (like I have defined them in storyboard) before they are showing the text.
So the user sees empty labels for a short moment before the actual data is loaded into the labels.
How can I fix this behavior?
Is it possible somehow to access the outlets immediately after instantiating the view controller from storyboard, without using Timing.performAfterDelay(0)?
Help would be appreciated.
Outlets are set after view is loaded i.e. when viewDidLoad gets called on the view controller. However, calling it directly like vc.viewDidLoad() will not work, you have to access the view controller's view like let dummyVariable = vc.view instead. Here's the code that force loads the view and then sets the label values.
class func newViewControllerWithData(data: AnyObject) -> UIViewController {
let vc = storyboard.instantiateViewControllerWithIdentifier("MyOverlayViewController") as! MyOverlayViewController
let _ = vc.view // force load the view
// now set your outlets as you please
vc.titleLabel.text = data[...] // Load title label text
vc.countLabel.text = data[...] // Load count label text
return vc
}
NOTE: This is not really a good practice though. MyOverlayViewController should be responsible for setting its label values instead of these being set from the outside. You could pass it the required data via a property or argument to a method, etc.
When I go to a viewController I call within my viewDidAppear Method a function:
override func viewDidAppear(animated: Bool) {
getLessons()
}
This methods loads from parse.com a list of data I want to use in a pickerView.
The function itself:
func getLessons(){
var query = PFQuery(className:"Lesson")
query.orderByAscending("name")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
var name = object["name"] as String
self.languagePickerKeys.append(object.objectId)
self.languagePickerValues.append(name)
self.selectedLanguage.text = self.languagePickerValues.first // set the first lessons name into the text field
self.selectedLessonObjectId = self.languagePickerKeys.first // set the first objectId for the lesson
self.languagePicker?.reloadAllComponents()
}
} else {
// Log details of the failure
println("\(error.userInfo)")
}
}
println("getLessons done")
}
The thing is, that the textfield is empty, as the getLesson() gets the data async and the data is not available to the textfield.
I also tried to put the getLesson into the viewDidAppear method, but this doesn't help me, the textfield is empty anyway.
What can I do, to have the data from the getLessons() method ready and loaded its first value into my textfield when the view is shown to the user?
You certainly have to get the data from asyncTask before setting it to pickerView.
Here's the ViewController lifecycle after instantiation:
Preparation if being segued to.
Outlet setting
Appearing and Disappearing.
So, you have two options:
Load the data in previous ViewController and then perform the segue. You need to follow these steps for it.
a. Create a segue from previous ViewController to your ViewController.
b. Call the function when you want to go next ViewController which fetches the data, and the end (after getting the data) call performSegueWithIdentifier which will lead to your ViewController.
c. Set the data in prepareForSegue
let navigationController = segue.destinationViewController as UINavigationController
navigationController.data = yourData //you got from async call
Now when you reach your ViewController, you are sure that your data is present, and you can set it to your pickerView.
If you want to do it in the same ViewController: here's is the lifeCycle of ViewController:so you need to call your function in viewDidLoad, and always set your pickerView after completion of the async network call.
Make sure that you initiate all changes to the UI from the main thread e.g. like so:
dispatch_async(dispatch_get_main_queue(), {
selectedLanguage.text = languagePickerValues.first
self.languagePicker?.reloadAllComponents()
})
The problem is that findObjectsInBackgroundWithBlock is an asynchronous method, so even if you fire it in the ViewDidLoad you will never know when you will receive the response data and you can't be sure that the data will be ready by the time you view appear.
I think you have just 2 possibility:
The first one is to load the data in the previous view controller and then just pass the data that got ready to you view controller.
The second is to use a synchronous method (the findobject method maybe?) and put the call in a method that is fired BEFORE the view appear (like the viewWillAppear: method). But your view will stuck for a moment (I think) while the data is retreiving... However this second solution probably resolve your problem but using synchronous method to retrieve data from a slower data source is usually bad design solution.
D.