Using the same property name/reference for Outlets in a base class and potential bugs - ios

So I have a base class UIViewController called UITabBarTopLevelViewController:
class UITabBarTopLevelViewController: UIViewController {
#IBOutlet weak var uiNavItemTitle: UINavigationItem!
override func viewDidLoad() {
super.viewDidLoad()
uiNavItemTitle.titleView = CoreUtility.LogoForUINavBarGet()
// Do any additional setup after loading the view.
}
I then have two UIViewControllers that inherit from my base class and both look like this, except the second is called MyViewController2:
class MyViewController1: UITabBarTopLevelViewController {
//#IBOutlet weak var uiNavItemTitle: UINavigationItem!
override func viewDidLoad() {
super.viewDidLoad()
//uiNavItemTitle.titleView = CoreUtility.LogoForUINavBarGet()
}
I add a Navigation Bar object to each child UIViewController super view and then I CTRL drag and add a new outlet to each UIViewController child class:
And here is the second CTRL drag outlet:
These are different references, but I can comment out the #IBOutlet weak var uiNavItemTitle: UINavigationItem! in my child classes and reference one time only in the base class UITabBarTopLevelViewController, for both MyViewController1 and MyViewController2.
You can see, when I hover over the outlet circle, in my base class, it highlights both UINavigationItem in the Story Board:
I am surprised this works, its nice for me that it works because I only need to set the uiNavItemTitle.titleView for my logo one time for both views. This is what I want but there seems to be a bit of magic that I can reference multiple outlet references one time in my base class and there is no bugs or crashes.
I currently have no memory leaks or crashes and my app is working just fine and doing exactly as I desire.
Will there be any bugs with this?
Could someone explain how this is working?
Is the fact that this works, not surprising to experienced Swift developers?

That's how subclass exactly works.
You placed a UINavigationItem in the base class UITabBarTopLevelViewController through
uiNavItemTitle.titleView = CoreUtility.LogoForUINavBarGet()
Also, MyViewController1 and MyViewController2 inherit from the base class UITabBarTopLevelViewController. That's say these child viewControllers both have a UINavigationItem which inherit from their UITabBarTopLevelViewController.
This is not a bug, on the other hand, more like a topic about design pattern though. You could place all the base stuff into a base class, inherit from those classes and implement the specific detail within the child class.
HTH.

Related

One storyboard for few view controllers

I have a storyboard, and it needs to be used in few places. Is there any other option than just duplicate storyboard and assign it to new view controller? I want to avoid boilerplate code and writing 3x same outlets and others. I need exactly same layout and almost all functions in all three controllers, but I want to only change model for all of them.
I have tried to do it like this:
class FirstViewController: UIViewController {
//outlets
// code
// tableView configuration
}
class SecondViewController: FirstViewController {
// and here I have just overrided some function to modify them
}

How can an outlet be nil even though it has been set

I have a viewController with two embedded viewControllers through containers. I then created outlets from the parent viewController's containers to the parent class. I want to either hide or show the containers depending on certain conditionals.
But if I simply write:
#IBOutlet var twoArmsContainer: UIView! {
didSet {
print("SETTING TWO ARM")
}
}
override func viewDidLoad() {
super.viewDidLoad()
twoArmsContainer.isHidden = true //container is nil
}
Then it crashes with twoArmsContainer being nil after the print in didSet has been triggered. How is it possible that the outlet is set, but then becomes nil? I have tried hiding it inside didSet and that works fine:
#IBOutlet var twoArmsContainer: UIView! {
didSet {
print("SETTING TWO ARM")
twoArmsContainer.isHidden = true //WORKS
}
}
What else can I say? The class I'm working in inherits from another class so there is a super.viewDidLoad. Not sure if that is relevant. I tried putting the outlets in the super class but with the same results. I also tried removing and readding the outlets again. Have never experienced this problem before. Let me know if I should show more code; perhaps the entire class. Not really sure what's relevant as I'm clueless of where to start.
Ok, so I found out what was wrong; an issue impossible to detect without access to the code, so in retrospect I should have posted both the parent class and container class. Sorry about that.
Anyway the issue was that the container viewController inherited from the parent viewController. This enabled me to share code in the two container viewControllers.
So the structure was basically this:
class WizardChooseArmViewController: WizardViewController {
...
This is the parent viewController which inherits from a base viewController. Then the container viewControllers looked like this:
final class WizardTwoArmsViewController: WizardChooseArmViewController {
...
Apparently it's a bad idea for containers to inherit from its parent, so I refactored and changed it to:
final class WizardTwoArmsViewController: WizardViewController {
...
Not quite sure why its not possible for containers to inherit from its parent. Would be great if someone could brief me.

Deallocate view controllers in navigation controller that have a reference to self

Say I have view controllers A, B, C, D & E all embedded in a navigation controller. In view controller B, I have a custom UIImageView object. In C, I have a custom UITextfield object. Both custom classes have a reference to the view controller for various reasons such as I have to perform things like segue when a user taps the image view. To accomplish this, I have this inside each custom class file:
var controller: UIViewController?
And then inside each view controller, inside viewDidLoad I set that variable to self and everything works as expected (segues on tap etc..)
I have an unwind segue from E back to A. However, I noticed that due to these custom objects in view controllers B & C, both were not being deallocated due to a retain cycle caused by having this reference to the view controller. I fixed the issue by setting the controller variable to nil upon segue, however this creates a problem such that if the user goes back (pops the current view controller), because I set the controller variable to nil upon segue, nothing works (it wont segue again because controller var = nil). I thought I might fix this by adding viewWillAppear code as follows:
override func viewWillAppear(_ animated: Bool) {
usernameTextField.controller = self
passwordTextField.controller = self
}
Because I read that viewWillAppear will be called each time the viewcontroller comes into view. This did not fix the problem.
Any ideas on how to go about this? How can I set the controllers to nil during the unwind maybe...?
As the other answers have said you need to make it a weak reference like this:
weak var controller: UIViewControler?
However I would go further and say that you should not be keeping a reference to to a UIViewController inside any UIView based object (UIImageView, UITextField, etc). The UIViews should not need to know anything about their UIViewControllers.
Instead you should be using a delegation pattern. This is a basic example:
1) Create a protocol for the custom UIImageField like this:
protocol MyImageFieldProtocol: class {
func imageTapped()
}
2) Then add a delegate like this:
weak var delegate: MyImageFieldProtocol?
3) Your UIViewController then conforms to the protocol like this:
class MyViewController: UIViewController, MyImageFieldProtocol {
}
4) Somewhere inside the view controller (viewDidLoad is usually a good place you assign the view controller to the image views delegate like this:
func viewDidLoad {
super.viewDidLoad()
myImageView.delegate = self
}
5) Then add the function to respond to the protocol action to the view controller like this:
func imageTapped {
self.performSegue(withIdentifier: "MySegue", sender: nil)
}
var controller: UIViewController? should be a weak reference. Like this:
weak var controller: UIViewController?
To know more about that read about Resolving Strong Reference Cycles Between Class Instances in Swift's documentation.
You should use weak references when you keep some ViewControllers
weak var controller: UIviewControler?
You should check everything link to retain cycle, and referencing in swift :
https://krakendev.io/blog/weak-and-unowned-references-in-swift
https://medium.com/#chris_dus/strong-weak-unowned-reference-counting-in-swift-5813fa454f30
I had similar issues, I advice you to look at those link : How can I manage and free memory through ViewControllers

Inherit outlets and actions in view controller

i've create TabBar App with 5 tabs, all 5 views are the same and each view include buttons to control one set of equipment, switch input to output on receiver and control volume, so 5 views correspond to 5 rooms.
Now i have 5 different UIViewCOntrollers with nerly the same code, inherited from UIViewController.
But i think it should be one RoomViewController, inhereted from UIViewController, and RoomViewCOntroller shold be used for each room, something like this
class RoomViewController: UIViewController {
#IBOutlet weak var videoSat: UIButton!
func lockAudioLabel(sender: UIButton) {
if sender.isSelected {
return
}
videoSat.isSelected = false
}
#IBAction func videoSatSelect(_ sender: UIButton) {
//send command to equipment
//make some actions
lockVideoLabel(sender: sender)
}
}
And Room1ViewController is
class Room1ViewController: RoomViewController {
}
All rooms have scene in storyboard
So the question is there any way to use one set of iboulets and ibactions in RoomViewController class and inherit them for example in Room1ViewContoller? And how to access them?
So the question is there any way to use one set of iboulets and ibactions in RoomViewController class and inherit them for example in Room1ViewContoller?
The #IBOutlet properties and #IBAction methods you declare in RoomViewController are inherited by subclasses such as Room1ViewController.
But what is not inherited is the design in the nib. When you instantiate a subclass like Room1ViewController, it loads its own view, not some other view controller's view. This loading takes place on a per-instance basis. Therefore, you still need to hook up the interface objects in the nib to the properties and methods in the class declaration. In other words, you still need to make outlets and actions in the nib, because this is a different view.

How do I subclass a variable with an IBOutlet?

All descendants of my specific class are to have a UILabel instance variable. So in my parent class I have var label: UILabel. I want to have it in the sublclass as well, but as an IBOutlet. How do I do this?
I added the IBOutlet of the same name, and added weak to both variable declarations. But I get an error about "Cannot override with a stored property".
How should I be doing this? And is it possible to not have to instantiate the superclass' version as I just want it for subclassing?
By doing so you are re-declaring the label property in a subclass. IBOutlet is just a compiler notion for working with the Interface Builder.
In Swift stored properties can not be overridden or redeclared in a subclass. They can only be inherited.
However you can override the getter and setter of a properties in subclasses to provide extra validations or functionalities. Refer to the Swift guide: override getter setter
You need to declare your property in superClass with IBOutlet.
Or you can make a different property in your subclass. As also there is no meaning if you are connecting your property in one of subclasses(super class may have other) and you are not providing this implementation to other subclasses of your superclass.
EDIT: You can also set label outlet to two different viewControllersof your SuperClass from story board if you give Subclasses names in storyboard to different view Controllers.
Just define
class SuperClass{
#IBOutlet weak var label: UILabel! = nil
}
SubClass1 repersent view controller1 in storyboard derived from SuperClass
SubClass2 repersent another view controller2 in storyboard derived from SuperClass
Than Go to Assistant Editor and open SuperClass one side and other side view controller1 and connect outlet from SuperClass to label in storyBoard in view controller1.Drag from SuperClass label to storyBoard in view controller1
Now again open SuperClass one side and other side view controller2 and connect outlet from SuperClass to label in storyBoard in view controller2.Drag from SuperClass label to storyBoard in view controller2
If you click on SuperClass outlet than you will see two labels conneted to different viewControllers
Just add the IBOutlet modifier in the superclass.
Agreed that the short answer is simply:
"Just add the #IBOutlet modifier in the superclass."
One note though: if you will be exposing the ViewController in which the #IBOutlet is defined via a framework (with Carthage), you will need to add the public attribute to the variable definition. EDIT: instead, use open access keyword`.
Let's say you have a view controller CustomViewController subclassing UITableViewController, which you will build in the Carthage-delivered framework. Everything which should be accessible to the consumer application should be tagged public:
public class CustomViewController: UIViewController {
override public func viewDidLoad() {
super.viewDidLoad()
}
#IBOutlet public var myBackgroundView: UIView!
#IBOutlet public weak var myLabel: UILabel!
The nice thing about this approach (and I'm totally on-board with this use case) is that you can do the IBOutlet mapping in Interface Builder on the superclass, then switch the class of the Scene to the subclass, while retaining all of the mappings.
E.g.: SubclassedCustomViewController
import UIKit
import mymodule
class SubclassedCustomViewController: CustomViewController {
override func viewDidLoad() {
super.viewDidLoad()
myBackgroundView.backgroundColor = UIColor.blueColor()
myLabel.text = "My special subclass text"
}
I realize that this question was posted a while ago, but since I just struggled with the same issue and finally came up with a solution, I figured I would still post my findings...
I understand the problem to be as follows (at least that's the one I solved):
How to have class A inherit from class B, with each class having its own XIB file with the some common IBOutlet properties? The goal being to be able to have the super class handle the actions related to the IBOutlets that are common to its subclass(es), while still being able to use Interface Builder to design the interface for the subclass(es).*
In order to do so:
Make the IBOutlet connections in the superclass from the
superclass' XIB files
Make the IBOutlet connections in the subclass from the subclass' XIB
files, with the same IBOutlet property names as in the superclass for the ones you need to inherit.
Delete the declaration of the IBOutlet variables in the subclass

Resources