I'm developing simple app, where the user logins and can read articles from web. I recently added a code which sets a title at a second view while processing a segue, but my title at the second page is unfortunately nil. I have an object in storyboard connected properly to the variable in view controller, I checked this twice. I have no idea what to do, maybe I have unwrapped something not properly.
Code:
import UIKit
class MainViewController: UIViewController, UITabBarDelegate, UITabBarControllerDelegate, UIToolbarDelegate {
#IBOutlet var titleLabel: UILabel!
#IBOutlet var newsBar: UIToolbar!
#IBOutlet var accountBar: UITabBar!
override func viewDidLoad() {
super.viewDidLoad()
accountBar.delegate = self
newsBar.delegate = self
titleLabel.backgroundColor = UIColor(netHex: 0x00B0E4)
titleLabel.textColor = UIColor.whiteColor()
titleLabel.font = UIFont.boldSystemFontOfSize(16.0)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
return true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let next: SectionViewController = segue.destinationViewController as! SectionViewController
switch segue.identifier! {
case "hardwareSectionSegue":
next.titleLabel.text = "Rubrika o hardwaru"
case "softwareSectionSegue":
next.titleLabel.text = "Rubrika o softwaru"
case "webSectionSegue":
next.titleLabel.text = "Rubrika o webových technologiích"
case "programmerSectionSegue":
next.titleLabel.text = "Rubrika o programování"
case "mobileSectionSegue":
next.titleLabel.text = "Rubrika o mobilních zažízeních"
default:
next.titleLabel.text = "Rubrika nenalezena"
next.titleLabel.backgroundColor = UIColor.redColor()
}
}
#IBAction func unwindSegue(unwindSegue: UIStoryboardSegue) {}
}
The exception occurs below the first case where I'm trying to assign a value to next.titleLabel.text. It says the titleLabel is nil, which it souldn't be.
The SectionViewController:
import UIKit
class SectionViewController: UIViewController {
#IBOutlet var titleLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I'm pretty sure that it's caused by that type cast, but then how do I properly set the attribute, if the segue doesn't even know what type will the next view have?
I can see what you're trying to do, but I'm afraid iOS doesn't let you do things that way. In your prepareForSegue() method you're trying to modify the view controller that is being created by setting a text value directly.
iOS lazy loads as much as it can, which means at this point in your program the label hasn't actually been created – it really is nil. If you put a breakpoint on that line you should be able to run po next.titleLabel in the debugger window to see nil come back. If you run po next.view first it will force iOS to create the view and all its subviews, at which point running po next.titleLabel again will work as you expect.
If you try creating a template Master-Detail Application project in Xcode you'll see the correct solution: set a property in your new view controller, then transfer that value to your label in viewDidLoad().
Summary: When you're navigating from view controller A to view controller B, don't make A try to configure B's UI. Instead, send B the values it needs, and have B configure itself.
Related
I have a TextLabel in the ViewController VC, which will receive the input of the user whenever the user has put text on there. Then, pressing a button, that text that was on the TextLabel, will pass onto a Label at the SecondaryView VC. But the thing is that I have tried multiple ways to set the text from the TextLabel to the Label on the SecondaryView VC.
This is the first way I tried:
This is my ViewController.swift file.
class ViewController: UIViewController {
var text: String = ""
#IBOutlet weak var mainViewTextLabel: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is SecondaryView {
let vc = segue.destination as? SecondaryView
vc?.text = "\(mainViewTextLabel!)"
}
}
}
#IBAction func onButtonTap(_ sender: Any) {
}
}
And this is my SecondaryView.swift file:
import UIKit
class SecondaryView: UIViewController {
var text:String = ""
#IBOutlet weak var secondaryViewLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
secondaryViewLabel?.text = text
}
}
When I run the app and type any text in the TextField and press the Button to go to the SecondaryView VC, there is no text on there.
If someone knowns another way to pass text from a View to another, or a way that the text can appear, I would appreciate it.
You have a couple of issues, I think.
Firstly, you are calling prepare(for:...) within viewDidLoad. This function isn't something you call yourself. It's something that you provide an implementation for and the system calls it just before the segue.
The second is that you are passing a reference to a UITextField rather than the text of that text field.
Maybe something like this would be better:
class ViewController: UIViewController {
var text: String = ""
#IBOutlet weak var mainViewTextLabel: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do anything else you need to do when loading
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is SecondaryView {
let vc = segue.destination as? SecondaryView
vc?.text = mainViewTextLabel.text ?? ""
}
}
}
It looks like you are not passing through the text of the UITextField. Instead you are passing through a reference to the UITextField as a string.
What you want to do is access the .text property:
vc?.text = mainViewTextLabel.text!
The .text property returns a String optional or String? and it is generally bad practice to force unwrap it, since it could crash your application. Instead, you can use a guard/let statement to make sure it is not null. So:
vc?.text = "\(mainViewTextLabel!)"
can be replaced by:
guard let textFromTextField = mainViewTextLabel.text else {
return
}
vc?.text = textFromTextField
This is my protocol
protocol PassDataDelegate {
func passData(data: String)
}
My first controller
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
var delegate: PassDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
delegate = SecondViewController()
}
#IBAction func sendDataButtonTapped(_ sender: Any) {
delegate?.passData(data: textField.text!)
performSegue(withIdentifier: "Go", sender: nil)
}
}
And second, final one
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func passData(data: String) {
print("This came from first: \(data). Will change UI.")
myLabel.text = data
}
}
App is crashing on label changing part. It says nil while unwrapping optional. What is wrong here?
SecondViewController() is not the controller designed in the storyboard. It's a brand new instance without connected outlets (which is the reason of the crash). You need the real reference to the SecondViewController instance.
Assuming the SecondViewController instance is the destination view controller of the segue you don't need protocol / delegate, pass the data through the segue
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBAction func sendDataButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "Go", sender: nil)
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Go" {
let secondController = segue.destination as! SecondViewController
controller.passedData = textField.text!
}
}
}
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
var passedData = ""
override func viewDidLoad() {
super.viewDidLoad()
print("This came from first: \(passedData). Will change UI.")
myLabel.text = passedData
}
}
There are several fundamental issues with your code.
I think there might also be some misapprehensions on your side regarding delegation and UIStoryboardSegue mechanism. You should probably read up on that here (Delegation) and here (Segues).
That being said, let me post a solution to your problem with inline comments explaining what's going on.
// Has to be marked as a class protocol (`: class`) so that
// `weak var delegate: PassDataDelegate?` can be weak.
protocol PassDataDelegate: class {
func passData(data: String)
}
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
// Important!
// Make this a `weak` var. In your case, you would fortunately not create a retain cycle
// but there is a big threat of creating those when using delegation patterns with non-weak delegates.
//
// In your case, if you don't make this `weak`, `SecondViewController` would never be deallocated unless you
// cleared this var manually (setting it to `nil`).
//
// Also note that, if you're using `PassDataDelegate` solely for forwarding some data to the next view controller,
// you can dismiss this var entirely. There is no need to have a reference to the second view controller hanging around.
// In fact, as mentioned above, it can be dangerous to do so.
// Additionally, you don't need to make the protocol `: class` then.
private weak var delegate: PassDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// It doesn't make any sense to create a `SecondViewController` here.
// The segue mechanism will create a new instance of `SecondViewController`.
// delegate = SecondViewController()
}
#IBAction func sendDataButtonTapped(_ sender: Any) {
// `delegate?` is `nil` here.
// delegate?.passData(data: textField.text!)
performSegue(withIdentifier: "Go", sender: nil)
}
// This is the proper 'hook' for you to forward data or do anything with a destination
// view controller presented using `UIStoryboardSegue`.
// This method will be called by the system following your call to `performSegue`.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// `UITextField.text` can be `nil`, so safeguard for that here.
// If the destination implements our protocol, we can forward data to it.
if let text = textField.text, let delegate = segue.destination as? PassDataDelegate {
// This is optional. You can hang on to the destination view controller here, but
// review the comments above to reason about whether this makes sense or not.
self.delegate = delegate
// We can safely forward the data (text) here.
delegate.passData(data: text)
}
}
}
SecondViewController can stay as is.
Update
Regarding Delegation
The delegation pattern usually describes a back pointer which talks back to an initiating instance. E.g. using UITableViewDataSource, a UITableView talks back to a thing implementing this protocol to get information about its data and so on.You are essentially doing the opposite here by forwarding data to SecondViewController. As mentioned in the comments, this code even breaks, because the implementation of passData in SecondViewController is using outlets not yet initialised.
Now you can do one of three things here:
1
Keep the pattern you are using right now (which is not delegation to be precise) and change SecondViewController to make things work
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
private var data: String?
override func viewDidLoad() {
super.viewDidLoad()
// It is safe to access `myLabel` in `viewDidLoad`. Outlets have been connected.
if let data = data {
myLabel.text = data
}
}
func passData(data: String) {
self.data = data
// Only access `myLabel` if the view is loaded.
if isViewLoaded {
print("This came from first: \(data). Will change UI.")
myLabel.text = data
}
}
}
This approach is very cumbersome actually, because you need to manoeuvre around the fact that passData may be called at any time. So you don't know if your outlets have been initialised yet, which leads to bloated and repetitive code. Bad.
2
Strip protocols entirely and use a more straightforward approach
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
// This is the proper 'hook' for you to forward data or do anything with a destination
// view controller presented using `UIStoryboardSegue`.
// This method will be called by the system following your call to `performSegue`.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// `UITextField.text` can be `nil`, so safeguard for that here.
// If the destination is a `SecondViewController`, we know that is has `public var data: String` and we can forward data to it.
if let text = textField.text, let destination = segue.destination as? SecondViewController {
// We can safely forward the data (text) here.
destination.data = text
}
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var myLabel: UILabel!
// Deliberatly marking this a `public` to make clear that
// you're intented to set this from the 'outside'.
public var data: String? {
didSet {
if isViewLoaded {
myLabel.text = data
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// It is safe to access `myLabel` in `viewDidLoad`. Outlets have been connected.
if let data = data {
myLabel.text = data
}
}
}
Again, there are things we don't like about his approach:
Still repeating code and having to check for isViewLoaded
You specifically wanted to use protocols, we don't do that here
We could work around the repetitive code issue by providing the data in an init of SecondViewController. However, since you're using segues, the storyboard will instantiate the destination view controller for you and you cannot gain control over that. Now you could strip using segues, but this quickly moves far away from your original question and is a totally different code only approach. So this is no good either.
3
Use protocols but apply the delegation pattern correctly.
protocol DataProvider: class {
func provideData() -> String?
}
protocol DataHandler: class {
var providerDelegate: DataProvider? { get set }
}
class FirstViewController: UIViewController, DataProvider {
#IBOutlet weak var textField: UITextField!
func provideData() -> String? {
return textField.text
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// If the destination is a `DataHandler`, we can set yourselves as its provider.
if let destination = segue.destination as? DataHandler {
destination.providerDelegate = self
}
}
}
class SecondViewController: UIViewController, DataHandler {
#IBOutlet weak var myLabel: UILabel!
weak var providerDelegate: DataProvider?
override func viewDidLoad() {
super.viewDidLoad()
if let data = providerDelegate?.provideData() {
// Safe to access `myLabel`, because we are in `viewDidLoad`.
myLabel.text = data
}
}
}
This approach is the most generic. Both parties don't care what exactly the handler and provider are. Note that in a classical delegation pattern, you would probably not have the DataHandler protocol and check for a concrete type (here SecondViewController) in prepareForSegue. However, this approach is more flexible while still having the delegation weaved into it. This approach is also the most robust from the SecondViewController point of view. Instead of having to handle passData at any moment, it can decide itself when to ask its delegate (DataProvider) for the data. This way, SecondViewController can reason about when all of its outlets, etc. are initialised and it is safe to process the data.
I have not found an exact solution for my problem. I need to pass data from one view controller to another view controller. The problem is, that after the segue, the passed string data does not appear in the label.
func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject) {
if (segue.identifier == "segue1") {
if let destination = segue.destination as? ResultsViewController {
destination.name = correctslabel.text!
The second controller: there is just variable and "name" and a UIlabel, which does not show the passed data.
import UIKit
class ResultsViewController: UIViewController {
#IBOutlet var namelabel: UILabel!
var name = ""
override func viewDidLoad() {
super.viewDidLoad()
name = namelabel.text!
}
}
I've tried many ways to do it, but none of them worked. Thank you
Finally worked for me to do an override func of it, which meant, that I had to change the sender from AnyObject to Any?.
Haven't you tried this?
// ResultsViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
namelabel.text = name
}
I have two view controllers. In the first one i have label and 4 buttons representing a color, and a button called next. I want to accomplish that when a user selects a color in the first view controller and clicks next, that the label in my next viewController will automatically have that color.
This is what i have so far but i can't make it work.
import UIKit
class ViewController: UIViewController {
var backGroundColor = UIColor()
#IBOutlet var face: UILabel!
#IBAction func red(sender: AnyObject) {
face.backgroundColor = UIColor.redColor()
}
#IBAction func green(sender: AnyObject) {
face.backgroundColor = UIColor.greenColor()
}
#IBAction func blue(sender: AnyObject) {
face.backgroundColor = UIColor.blueColor()
}
#IBAction func yellow(sender: AnyObject) {
face.backgroundColor = UIColor.yellowColor()
}
#IBAction func next(sender: AnyObject) {
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("face") as! Face2
vc.newFace.backgroundColor = self.face.backgroundColor
self.navigationController?.pushViewController(vc, animated: true)
}
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.
}
}
// My Face2 class:
import UIKit
class Face2: UIViewController {
var backGroundColor = UIColor()
#IBOutlet var newFace: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// var vc = ViewController()
self.newFace.backgroundColor = backGroundColor
}
}
Welcome to SO.
You are making a common design mistake.
Don't try to manipulate the second view controller's views directly. That's bad design, and often doesn't work.
Instead add a property/properties to your Face2 class.
In this case, a backgroundColor property is probably all you need.
Set that property in your next function, and then in your Face2 class's viewDidLoad method, use the color to set the background color of whatever views need to use the custom color. If you decide later that the set of views that need to change color is different, you can change the code for your Face2 class and the first view controller doesn't need to change at all.
You also need to get rid of vc2. That makes no sense. You create a new Face2 view controller vc with a call to instantiateViewControllerWithIdentifier. That's the one you should be configuring. The one you create in vc2 is never used for anything. It will get deallocated as soon as you exit the next() function.
my suggestion is to set a nsstring (string that you set when you press the color button) property to second view controller and in the second view controller view did load make a switch based on the passed string to set the background color
I am trying to create a simple application that consists of one Navigation controller with 2 screens controlled by a single UIViewController class. The app will allow the user to enter a number from 0 to 10 in a text field and when the user presses a button they will be taken to the 2nd screen showing if they guessed the randomly generated number. I get the "unexpectedly found nil while unwrapping an Optional value" error when I am accessing the text property of the label in the second screen. I dont uderstand why, I have connected the label with the class. Any suggetions?
This is my UI:
This is my Navigation controller class code:
class MyNavController: UINavigationController {
var ranNum:Int = 0
override func viewDidLoad() {
super.viewDidLoad()
ranNum = (Int)(arc4random_uniform(10))
NSLog("random number: \(ranNum)")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This is my Generic View controller class:
class GenericViewController: UIViewController {
#IBOutlet weak var inputTextField: UITextField!
#IBOutlet weak var outputLabel: UILabel!
var setThisLabel: String = "You Win!"
#IBAction func guessTheNumber(sender: AnyObject) {
var generatedRanNumber = (parentViewController as! MyNavController).ranNum
var userGuessNumer = inputTextField.text.toInt()
if generatedRanNumber == userGuessNumer {
outputLabel.text = "You Win!"
} else if generatedRanNumber < userGuessNumer {
outputLabel.text = "Think Less..."
} else {
outputLabel.text = "Think Big..."
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
change from
var generatedRanNumber = (parentViewController as! MyNavController).ranNum
to
var generatedRanNumber = (navigationController as! MyNavController).ranNum
also notice that arc4random_uniform(10) returns a number between 0 and 9. you tell the user to guess a number between 0 and 10.
Actually the problem is you are pushing from one instance of Generic View Controller to another without passing on the data. It is generally not recommended to use a generic subclass of UIViewController in this way. If you want your code to work as is, place the UILabel in the same view as your other UI and don't segue (i.e. only have one instance of GenericViewController). Or create two subclasses of UIViewController one called "GuessViewController" and the other called "AnswerViewController" and pass the guess value between them in prepareForSegue. GuessViewController would handle getting the string from the user and evaluate it. AnswerViewController would whether it's right or wrong. It is also generally not necessary to subclass UINavigationController. You could place that code in the viewDidLoad of GuessViewController.