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

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
}
}

Related

Update subviews of UIView from ViewController

I have a UIViewController that implements a custom UIView, so;
override func loadView() {
view = CustomView()
}
The custom view has a few lables and buttons and all the normal stuff, problem is in my viewController I have a request, and when that request is done, I'd like to update some of those lables/buttons.
Right now, in my CustomView, I have functions, such as;
func updateView() {
labelOne.isHidden = true
LabelTwo.isHidden = false
}
So I call the appropriate function from my viewController when the request is done.
This works, but it feels wrong, is there a neater way to update the subviews of my custom UIView, from my viewController? Should I maybe be using protocols or delegates?
One thing I've found quite neat in the past is passing the model directly to the custom view, then using didSet to trigger updates.
class CustomView: UIView {
let labelOne = UILabel()
let labelTwo = UILabel()
var object:CustomObject! {
didSet {
self.labelOne.text = object.name
self.labelTwo.text = object.description
}
}
...
}
This means in your UIViewController you can do the request and then pass the model straight to the custom view.
RequestHelper.getObject() { object in
self.customView.object = object
}
Obviously here I'm guessing at your request and object names but hopefully you get the idea.

Access TextField from another class or method without storyboard

Okay, this might be one of the most basic questions ever, but all answers I find use storyboard to declare an outlet for a label, textfield or whatever element that needs to be changed. I, however, don't use storyboards and write everything in code. Now I have a function setupViews, where I define a textfield:
let usernameInput = UITextField()
Now, I can perfectly set the text or placeholder or whatever inside this setupViews() class, but how can I access it outside? For example, if I have a function logIn(), I want to call usernameInput.text and use it in this function.
Someone who can point me in the right direction? Do I need to declare this textfield globally, in another file, or something else?
When I create my views in code I always associate a property with the view that has all those various display values.
I have not tested this code to see but hopefully the following will give you an idea.
import UIKit
struct {
var name: String
}
class CustomViewController : UIViewController {
// some struct which contains data for view
var customViewData : ViewDataInfo? {
didSet {
labelOnScreen.text = customViewData.name
}
}
var labelOnScreen: UILabel = {
let label = UILabel()
label.text = "Placeholder information..."
// stuff auto layout
label.translateAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
private func setupView() {
view.addSubview(label)
// set your constraints here
}
}

Strong references and UIView memory issues

I'm dealing with some deallocation issue and perhaps strong or circular referencing that can't figure out. I have three UIViews instantiating like below:
There is one main ViewController which I have added a UIView inside it in storyboard and the UIView has a weak outlet inside the class like:
class ViewController : UIViewController {
//MARK: - outlets
#IBOutlet weak var firstView: FirstUiview!
}
second UIView is added as a subview to the first view programmatically like:
class FirstUiview : UIView {
//creating an instance of secondUiView
lazy var mySecondView: SecondViewClass = {
let dv = SecondViewClass()
dv.backgroundColor = UIColor.red
return dv
}()
//sometime later by clicking on a button
self.addSubview(mySecondView)
//a button will be tapped to remove mySecondView;
//later will be called at some point upon tapping:
func removingSecondViewByTapping() {
if mySecondView.isDescendant(of: self) {
mySecondView.removeFromSuperview()
}
}
}
Now the SecondViewClass is :
class SecondViewClass : UIView {
//in this class I create bunch of uiview objects like below:
lazy var aView : UIView = {
let hl = UIView()
hl.tag = 0
hl.backgroundColor = UIColor.lightGray
return hl
}()
self.addSubview(aView) //... this goes on and I add other similar views the same way.
//creating an instance of thirdView
var let thirdView = UIView()
self.addSubview(thirdView)
}
Now if user taps the button to remove mySecondView and then add it again at some other time (still in the same ViewController) I expect all the subviews of mySecondView to have been released and gone but they are all there. I would appreciate it a lot if someone can point it to me where am I keeping a strong reference or if there is a circular referencing issue? or perhaps something else?
You have two strong references to your views, your custom property and the view hierarchy reference established when you call addSubview. When you remove the view from the view hierarchy, your class, itself, still has its strong reference to it.
You could solve this by making your reference optional, and when you call removeFromSuperview, also manually set your reference to nil. Or, perhaps easier, you might resolve this by using weak references, letting the view hierarchy maintain the strong references for you. And because your custom property is weak, when you remove it from the view hierarchy (thus eliminating the only strong reference to it), your weak reference will automatically become nil:
class FirstView: UIView {
weak var secondView: SecondView? // note the `weak` reference, which is obviously an optional
//sometime later by clicking on a button
func doSomething() {
let subview = SecondView()
subview.backgroundColor = .red
self.addSubview(subview)
secondView = subview
}
// a button will be tapped to remove secondView;
// later will be called at some point upon tapping ...
func removingSecondViewByTapping() {
secondView?.removeFromSuperview()
}
}

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.

Swift Master-Detail Template App Mystery

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"
}
}
}

Resources