Swift Master-Detail Template App Mystery - ios

I'm getting a Can't unwrap Optional.None error when running a slightly modified version of the Master-Detail App in Swift.
All I did was add a second UILabel to the DetailViewController, right under the pre-existing detailDescriptionLabel, and when I navigate to the DetailViewController from the MasterViewController I crash on the statement which sets my new Label:
secondLabel.text = "This is the Second Label"
I declare this label is as followed:
#IBOutlet var secondLabel : UILabel
What's really interesting is that the pre-existing code for setting the detailDescriptionLabel includes the new optional let syntax:
if let label = self.detailDescriptionLabel {
label.text = "This is the Detail View"
}
So why is it that we need a let statement here for detailDescriptionLabel? It was never declared as an Optional Label, it was declared like any regular Label IBOutlet property, like so:
#IBOutlet var detailDescriptionLabel: UILabel
so why is it being treated as an Optional?
And does this mean that from now on any object I add as an IBOutlet will also have to go through this sort of let statement if I want to set it through code?
EDIT:
I'm crashing in the following method, on the line anotherLabel.text = "Second Label":
func configureView() {
// Update the user interface for the detail item.
if let theCar: CarObject = self.detailItem as? CarObject {
if let label = self.detailDescriptionLabel {
label.text = theCar.make
}
anotherLabel.text = "Second Label"
}
}
but when I treat anotherLabel with the whole if let business, as follows, it works perfectly well:
if let label2 = self.anotherLabel {
label2.text = "Second Label"
}

Properties declared with #IBOutlet are always implicitly unwrapped optional variables. Apple's documentation explains it this way:
When you declare an outlet in Swift, the compiler automatically
converts the type to a weak implicitly unwrapped optional and assigns
it an initial value of nil. In effect, the compiler replaces
#IBOutlet var name: Type with #IBOutlet weak var name: Type! = nil.
The compiler
converts the type to an implicitly unwrapped optional so that you
aren’t required to assign a value in an initializer. It is implicitly
unwrapped because after your class is initialized from a storyboard or
xib file, you can assume that the outlet has been connected. Outlets
are weak by default because the outlets you create usually have weak
relationships.
Since it's implicitly unwrapped, you don't have to go through if let label = something every time, just know that if your label is nil and you try to work with it, you'll end up with a runtime error. I'm guessing your second label isn't hooked up in Interface Builder -- is that the case? [OP: Nope!]
Okay, what's happening in this specific case is that the configureView() method can get called from the master view controller's prepareForSegue(), since the detailItem property on the detail view controller has a didSet handler. When that happens, the detail view controller hasn't been loaded yet, so no labels have been created. Since the labels will get set up at the same time, you could put both initializations under that one if statement (and make it a bit more clear, even):
func configureView() {
// Update the user interface for the detail item.
if let theCar: CarObject = self.detailItem as? CarObject {
if self.detailDescriptionLabel != nil {
self.detailDescriptionLabel.text = theCar.make
self.secondLabel.text = "Second Label"
}
}
}

Related

Swift: Cannot change properties in instantiated view controller [duplicate]

This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 4 years ago.
I am totally new at Swift so what follows may be a very basic question. Plus it's my first one at stackoverflow.com!
I am getting the "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value" while setting text properties of UILabels in an instantiated view controller.
I am making a simple app to show a list of medicines i have at home and their expiry dates. A table view controller shows a list of the medicines, from an array called medArray, and when each one is tapped I want it to load a detail screen with some further details.
I set up a DetailView controller using IB and created the outlet connections of 3 UILabels:
// DetailView.swift
// HomeMed
//
// Created by Joao Boavida on 10/12/2018.
// Copyright © 2018 Joao Boavida. All rights reserved.
//
import UIKit
class DetailView: UIViewController {
#IBOutlet var nameLbl: UILabel!
#IBOutlet var subtextLbl: UILabel!
#IBOutlet var expdateLbl: UILabel!
}
I think the connections are successfully made as the dots on the left gutter are solid. I do get the error when tapping on a table view cell, which calls the following code:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let medicine = medArray[indexPath.row]
if let vc = storyboard?.instantiateViewController(withIdentifier: "DetailView") as? DetailView {
vc.nameLbl.text = medicine.name
if let subtext = medicine.subtext {
vc.subtextLbl.text = subtext
} else {
vc.subtextLbl.text = ""
}
let ISOdateFormatter = ISO8601DateFormatter()
ISOdateFormatter.formatOptions = [.withMonth, .withYear, .withDashSeparatorInDate]
vc.expdateLbl.text = "Exp: \(ISOdateFormatter.string(from: medicine.expiry))"
navigationController?.pushViewController(vc, animated: true)
}
}
I get the error as soon as I try to access vc.nameLbl.text, which is nil for reasons I don't know. vc.expdateLbl and vc.subtextLbl are also nil.
The "vc" view controller seems to be instantiated correctly because if I omit the configuration code and push it just after creating it it displays fine, albeit with the initial text in the labels that I set in IB. It is only when I try to change those labels that it crashes.
I have tried making and remaking the outlet connections with no success. Can anyone help troubleshooting this?
Thank you very much!
You are setting your VC's outlets here, right?
vc.nameLbl.text = medicine.name <---
if let subtext = medicine.subtext {
vc.subtextLbl.text = subtext <---
} else {
vc.subtextLbl.text = "" <---
}
...
vc.expdateLbl.text = "Exp: \(ISOdateFormatter.string(from: medicine.expiry))" <---
The outlets nameLbl, subtextLbl and expdateLbl are all nil when the above code is run. They are not set at that point in time yet.
The outlets are already set when viewDidLoad is called. Therefore, what you can do is to add three properties in your DetailView:
var name: String!
var subtext: String!
var expdate: String!
Instead of setting the outlets, you set the above three properties:
vc.name = medicine.name <---
if let subtext = medicine.subtext {
vc.subtext = subtext <---
} else {
vc.subtext = "" <---
}
...
vc.expdat = "Exp: \(ISOdateFormatter.string(from: medicine.expiry))" <---
Then, in the DetailView, assign the properties to the text of the labels in viewDidLoad:
override func viewDidLoad() {
nameLbl.text = name
subtextLbl.text = subtext
expdateLbl.text = expdate
}
It crashes because your are trying to access a UILabel before it is loaded. So, you should call vc.loadViewIfNeeded(). This will load all the UIViewController views.

How to set the delegate of a subview

I have a custom view created by subclassing UIView, and it has two UITextView subviews which are instantiated inside its init method. Let's say for clarifying things, that I have a superview A containing two UITextViews B and C.
On the other hand, I have the UIViewController VC that manages the superview A, and its responsible of its control. This means that it has the method that controls what happens when the user changes text on C.
When someone has to control a UITextView, he usually does something like
textView.delegate = self
I want to do the same, but C is not visible from VC. VC just uses A, so I have
a.delegate = self
and then I have come with the idea of doing this inside the implementation of A:
weak var delegate: UITextViewDelegate? {
get {
return c.delegate
}
set {
c.delegate = newValue
}
}
But this is giving me an error, saying that it's unwrapping an optional value whose actual value is nil.
So, what's the correct way of controlling UITextView C (that is inside UIView A) from VC, which only has an instance of A.
It is most probably that c maybe nil. To solve this, you need to set the delegate after c is initialised.
Alternatively, create your own delegate - ADelegate! This way you can name your own delegate methods with more meaningful names!
protocol ADelegate : class {
func cDidChange()
}
In A:
weak var delegate: ADelegate?
Now A should implement UITextViewDelegate, set c.delegate to self, and relay the methods to self.delegate.
If you have added the UITextField's in the Xcode Interface Builder, you can create outlets for the textfields in your ViewController (even when the textfields are sub-sub-children of the view, that the viewcontroller controls).
Thus continuing with your naming, you add the following outlets to your ViewController:
#IBOutlet weak var B: UITextField!
#IBOutlet weak var C: UITextField!
and in your ViewController's viewDidLoad() add:
B.delegate = self
C.delegate = self
If you created the UITextField's programmatically you can set their tag value to e.g. 1 and 2(0 is most likely already used) and then get the view from your ViewController like this:
let B = self.view.viewWithTag(1) as? UITextField
let C = self.view.viewWithTag(2) as? UITextField
Of course, make sure that the ViewController extends UITextFieldDelegate, that is:
class ViewController: UIViewController, UITextFieldDelegate {...}

Swift 2.2 - Updating UILabel throws "unexpectedly found nil while unwrapping an optional value"

I am new to iOS development so forgive me if I'm missing something obvious. I have a view controller that contains a subview in which I've created a numpad, and for the time being I want to give the numpad view its own UIView subclass because I want to do a few different things with it. Right now the numpad is just creating a string from the keys that get pressed, and I've set up a delegate to pass that string anywhere else I want to use it (though I've also tried accessing the raw input directly in the view controller with let a = subview(); label.text = a.rawInput).
Whenever I try to set the text of the UILabel in the view controller to the subview's raw input, whether by delegation or directly, the UILabel is found to be nil and throws the error in the title.
Things I've tried:
Setting the text inside a viewDidLoad override, and outside of it
Setting a variable (testInput) inside the view controller to adopt the subview's raw input and setting the label text to that (I've confirmed that the variable inside the view controller gets properly set, so no delegation issues)
Using didSet on the testInput variable both to set label text to testInput and to try calling viewDidLoad and set the label text in there (printing testInput inside this didSet does print the right string, FWIW)
Deleting and relinking the IBOutlet for my label
Strong and weak storage for the IBOutlet variable
Trying to do the same thing in another subview within the view controller, in case for some reason it was the view controller's own fault
Searching everywhere for a solution that works
I'm stumped. Here is my relevant numpad code:
import UIKit
protocol NumpadDelegate {
func updateInput(input: String)
}
class Numpad: UIView {
// MARK: UI outlets
#IBOutlet weak var decButton: UIButton!
// MARK: Properties
var rawInput: String = ""
var visibleInput: String = ""
var calcInput: String = ""
var operandReady = 1
var percentWatcher = 0
var delegate: NumpadDelegate? = BudgetViewController()
// MARK: Functions
func handleRawInput(str: String) {
rawInput += str
print("numpad input is \(rawInput)")
delegate?.updateInput(rawInput)
}
And here is the view controller code:
import UIKit
class BudgetViewController: UIViewController, NumpadDelegate {
// MARK: Properties
//#IBOutlet weak var transactionValueField: UITextField!
#IBOutlet weak var remainingCashForIntervalLabel: UILabel!
#IBOutlet weak var intervalDenoterLabel: UILabel!
#IBOutlet weak var currencyDenoterLabel: UILabel!
#IBOutlet weak var mainDisplayView: TransactionType!
#IBOutlet weak var inactiveInputView: InactiveInput!
#IBOutlet weak var numpadView: Numpad!
#IBOutlet weak var rawInputLabel: UILabel!
var remainingCashForInterval = 40
let display = TransactionType()
var testInput = "" {
didSet {
viewDidLoad()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// let numpad = Numpad()
// numpad.delegate = self
// print("\(numpad.delegate)")
self.rawInputLabel.text = testInput
}
func updateInput(input: String) {
print("view controller now has \(input)")
display.mainInput = input
testInput = input
}
As a side note, in case you noticed that my protocol isn't a class type, for some reason adding : class to it and declaring my delegate as a weak variable prevents the delegation from working. Any suggestions there?
You assigned the delegate like so:
var delegate: NumpadDelegate? = BudgetViewController()
That doesn't reference the view controller whose scene was presented, but rather a new blank one. And that's why when you used weak, why it was deallocated (because that orphaned instance of the view controller has no strong references to it).
You should define the protocol to be a class protocol again, and define delegate to be:
weak var delegate: NumpadDelegate?
And then, in the view controller's viewDidLoad, uncomment the line that sets that delegate:
numpadView.delegate = self
But, do not uncomment the line that says numpad = Numpad(); that is incorrect as that creates yet another Numpad instance. But you do want to set the delegate of the existing Numpad, though.
Both of these issues (namely, getting a reference to the view controller that is to be the delegate of the Numpad view; and getting a reference to the Numpad view that the storyboard presented) suggest some misunderstanding about the the process of presenting a storyboard scene.
The process is basically as follows:
the view controller is instantiated, using whatever class you specified as the base class for that scene;
its root view, as well as all of the subviews on that scene will be instantiated for you;
the storyboard will hook up the IBOutlet references in the scene's base class to the outlets you created; and
the view controller's viewDidLoad is called.
That's an oversimplification, but that's the basic process.
But the key is that all of these view controllers and views that are referenced on the storyboard scene are created for you. You don't want to try to create any of these yourself (and the presence of the () at the end of BudgetViewController() or Numpad() says "create a new instance of x", which is not what we want to do here).
So, when we need to get a reference to the view controller so that we can programmatically specify the delegate for one of the views, you can do this in viewDidLoad, at which point self references the view controller that the storyboard instantiated for us. We don't want to instantiate a new one. Likewise, when you want to reference the Numpad that the storyboard instantiated for us (in order to hook up its delegate), you use the IBOutlet you hooked up in Interface Builder, rather than programmatically instantiate a new Numpad with Numpad().

Strongly referenced variable may cause memory issues

I have been programming in Swift for a couple months now. Recently, I have focused more on concepts of how Swift as a language works.
Hence, recently while reading apple documentation on Automatic Reference Counting(ARC), I came across the following lines:
This one on top:
In most cases, this means that memory management “just works” in Swift, and you do not need to think about memory management yourself. ARC automatically frees up the memory used by class instances when those instances are no longer needed.
And in the next paragraph, the following:
To make this possible, whenever you assign a class instance to a property, constant, or variable, that property, constant, or variable makes a strong reference to the instance. The reference is called a “strong“ reference because it keeps a firm hold on that instance, and does not allow it to be deallocated for as long as that strong reference remains.
I am a little confused as to what is the dynamics of the situation. I have noted while using storyboards, that you set reference to weak, hence the class looks like this, also what I would call case 1:
Case 1
class SomeClass : UIViewController {
#IBOutlet weak var nameLabel : UILabel!
override func viewDidLoad() {
nameLabel.text = "something."
}
}
Here, the label has one-to-one weak reference with the ViewController, and as soon as the Controller is changed, reference is broken (memory dealloc) since it is weak. Hence, no issues related to the memory.
Pardon me if the above statement is wrong or loosely held. I would be glad if someone confirms my assumption or counters it.
My question is about the second case however, where I do not use storyboards and class looks like below:
Case 2
class SomeClass : UIViewController {
var nameLabel : UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
view.addSubView(nameLabel)
// view.addConstraints...
}
}
For the above case, My assumption is that the ViewController has one-on-one strong reference with the label, and the view inside ViewController also has strong reference with the label.. If the class is changed/ label is removed from subview.. then I think the memory would not be deallocated. Or at least the view controller will maintain a strong reference to the label (as per the docs.)
I confirmed this by removing label from view's subviews and printing out the label (It gave me an instance of UILabel with frame that was at 0 origin and 0 size.) hence an instance that isn't nil.
The only thing I could gather from this was that although the label was removed from UIView, it still maintained a strong reference with the controller, hence permanent state in memory. Am I right?
If this is the case. How should I prevent my code from having such memory issues? The bigger problem is that if I declare my variable like so, I get a nil while adding it as a subview of main view in controller.
weak var nameLabel : UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
If declaring variables like in the second case can cause permanent strong references how should I declare them instead to not have memory issues?
So to conclude, my question is:
In cases where no storyboard outlets are used, and variables are strongly referenced to the view controller, will these references cause memory issues?
If so, what code declaration practice must I follow?
If not so, please provide thoughtful arguments with valid explanations to counter it.
Again, pardon me if I am incorrect anywhere.
Thank you in advance.
The only thing I could gather from this was that although the label was removed from UIView, it still maintained a strong reference with the controller, hence permanent state in memory. Am I right?
No. There's no big issue here.
The label has no strong reference to the view controller — if it did, that would be a retain cycle and would cause both the label and the view controller to leak. For this very reason, a view should never keep a strong reference to its view controller.
Here, however, it's the other way around: the view controller has a strong reference to the label. That's fine. It's true that the label therefore stays in existence after it has been removed from its superview. But that might not be bad. In many cases, it's good! For example, suppose you intend to put the label back into the interface later; you will need to have retained it.
If you are sure you won't need to keep the label around later, then simply use an Optional wrapping a UILabel as your instance property. That way, you can assign nil to the label instance property when you're done with it, and the label will go out of existence.
But in any case there is no leak here and you should just stop worrying. When the view controller goes out of existence, the label will go out of existence too. The label lived longer than it had to, but that's tiny and unimportant on the grand scale of things.
create the label when you need ,then call addsubView to make an strong reference to it and make an weak reference to your member var like this:
class ViewController: UIViewController {
weak var label : UILabel?
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
view.addSubview(label)
self.label = label
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
print(label)
//click first Optional(<UILabel: 0x7fb562c3f260; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7fb562c11c70>>)
//click second nil
label?.removeFromSuperview()
}
}
anyway while the viewcontroller release ,the label will be release and view.subview will be release too.
Demo
i wrote an easy demo make the ViewControllerTest to be the rootviewcontroller
class Test{
weak var label:UILabel?
static let instance = Test()
}
class ViewControllerTest: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let item = UIBarButtonItem(title: "Test", style: .Plain, target: self, action: #selector(self.test))
self.navigationItem.rightBarButtonItem = item
}
func test(){
print(Test.instance.label)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let vc = ViewController()
self.navigationController?.pushViewController(vc, animated: true)
print(vc.nameLabel)
let test = Test.instance
test.label = vc.nameLabel
}
}
class ViewController: UIViewController {
var nameLabel : UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.whiteColor()
view.addSubview(nameLabel)
let item = UIBarButtonItem(title: "Test", style: .Plain, target: self, action: #selector(self.test))
self.navigationItem.rightBarButtonItem = item
}
func test(){
print(Test.instance.label)
}
}
I don't think strongly referenced variables to view controller cause any memory issues.
Normally views are deallocated before deallocating their view controller. For example, in in your code, when deallocating the view, ARC decreases the counter pointing to namelabel, so it passes from 2 to 1. Then, when deallocating the view controller it decreases the counter again, from 1 to 0. Once there are 0 references pointing to namelabel its removed.
A weak reference is a reference that does not keep a strong hold on
the instance it refers to, and so does not stop ARC from disposing of
the referenced instance. This behavior prevents the reference from
becoming part of a strong reference cycle. You indicate a weak
reference by placing the weak keyword before a property or variable
declaration
> Weak references must be declared as variables, to indicate that their
value can change at runtime. A weak reference cannot be declared as a
constant.
Because a weak reference does not keep a strong hold on the instance
it refers to, it is possible for that instance to be deallocated while
the weak reference is still referring to it. Therefore, ARC
automatically sets a weak reference to nil when the instance that it
refers to is deallocated. Because weak references need to allow nil as
their value, they always have an optional type. You can check for the
existence of a value in the weak reference, just like any other
optional value, and you will never end up with a reference to an
invalid instance that no longer exists
Source: Apple docs
A weak reference is just a pointer to an object that doesn't protect the object from being deallocated by ARC. While strong references increase the retain count of an object by 1, weak references do not. In addition, weak references zero out the pointer to your object when it successfully deallocates. This ensures that when you access a weak reference, it will either be a valid object, or nil.
Hope can help you to understand better a weak reference, be it related to a storyboard item or created programmatically.
I always explain it to my students like this.
With a strong reference, you can see a value, and you have a lasso around it. You have a say in whether the value remains alive.
With a weak reference, you can see it, but there's no lasso. You have no say in whether the value lives or not.
For your situation to avoid occurrence of Memory leak for a second. You can go with Matt answer.
For better understanding, create a custom UILabel class under MRC flag in build phases->Complie sources.
In custom class, override retain and release method. Put breakpoints on them.
Use that custom UILabel class in your view controller with ARC flag ON. Go with matt answer or use below optional declaration of UILabel.
import UIKit
class ViewController: UIViewController {
var label:UILabel? = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "something"
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.label!)
//namelabel goes out of scope when method exists.
//self.view has 1+ ref of self.label
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.label?.removeFromSuperview()//-1 ref of self.label
self.label = nil
print(self.label)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You will have clear picture of how ARC works and why weak ref of UILabel causes crash while adding to UIView.

When using Computed variable and Snapkit: No common superview between views

So here's the thing, I'm declaring a property like this:
var aNameLabel: UILabel {
guard let foo = Applicant.sharedInstance.realName else {
return UILabel(text: "获取姓名失败", color: .whiteColor())
}
return UILabel(text: foo, color: .whiteColor())
}
And when I try to add constraint to the aNameLabel after I did someView.addSubView(aNameLabel), the app would crash every time at this constraint-adding thing, and says No common superview between views
However, when I change the variable into a let constant like this:
let aNameLabel = UILabel(text: "Allen", color: .whiteColor())
The constraint will be added with no complaint. Somebody can help me with this?
UPDATE
With the help of #par , I've changed my code into this:
var aNameLabel: UILabel = {
guard let foo = Applicant.sharedInstance.realName else {
return UILabel(text: "BAD", color: .whiteColor())
}
return UILabel(text: foo, color: .whiteColor())
}()
And then the aNameLabel would always be assigned with value "BAD", while actually my guard let is successful. How do I fix this?
The problem is that you are creating a new UILabel every time you access the aNameLabel variable (a computed property function runs every time you access it). Presumably you are doing the same thing for the superview of this view (when you access someView in someView.addSubview() in your example above). If so, that's why there's no common superview and you are crashing.
You should create only one instance of each UIView used by your view controller, so creating a variable as a constant as you've shown is a great approach, or you can use a closure-initializer pattern like so:
var aNameLabel: UILabel = {
return UILabel(...)
}()
Notice in the above example the parentheses after the closing brace. Because it's a closure-initializer it will only be called once just like a let constant.
Often a UIView created with let isn't appropriate because constant properties need to be initialized before init() returns, and if you're creating views in a view controller you won't have a chance to add views until loadView() is called. In this case, declare your UIViews as implicitly-unwrapped optionals. They will be set to nil by init() which meets the initialization requirement, then you can set them to actual views later when viewDidLoad() is called, for example:
class MyViewController: UIViewController {
var someSubview: UIView!
override func viewDidLoad() {
super.viewDidLoad()
someSubview = UIView()
view.addSubview(someSubview)
// now set constraints with SnapKit
}
}

Resources