How do I subclass a variable with an IBOutlet? - ios

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

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
}

Embedding UIContainerView into table view and trying to access via #IBOutlet results in Unexpectedly found nil

Using Apple's rather old Swift Getting Started tutorial as a base, I've a working app. I'd like to add a UIView at the top of a table, to appear temporarily if an error occurs. Unfortunately, when trying to access UI elements from the UIView's associated class, I get an "Unexpectedly found nil while implicitly unwrapping an Optional value" error.
https://imgur.com/wfqFach.png
This is actually a UIContainerView, but apparently appears as a UIView.
I've subclassed both the UIView and the linked view (via an embed segue) with a subclass I called ErrorView, and added an #IBOutlet link to the UIView in the table view's subclass. The UI elements are linked to the ErrorView class with an #IBOutlet.
When accessing ErrorView class members via the #IBOutlet in the table subclass, everything is dandy until the ErrorView class tries to access its #IBOutlet linked elements, at which point it crashes with "Unexpectedly found nil while implicitly unwrapping an Optional value."
Strangely, accessing the view itself from within the Error Class is possible, and as such I'm able to do something view-related like change its colour without an error - though the colour change doesn't actually happen.
A very trimmed down version of the code is below, which should reproduce the issue.
I'm using iOS13 and Xcode 11 beta.
ErrorView.swift
import UIKit
class ErrorView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
#IBOutlet weak var uiMessage: UILabel!
func setErrorMessage(errorMessage: String){
uiMessage.text=errorMessage //<-- error here
}
}
ModuleTableViewController
import UIKit
import Foundation
class ModuleTableViewController: UITableViewController {
#IBOutlet weak var errorView: ErrorView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
errorView.setErrorMessage(errorMessage: "Hello")
}
}
In fact, container views contain View Controller not UIView, you need to set a UIViewController class to your error viewController, then make an outlet for you UIlable, then set a protocol and make you Error View controller conforms to it, when something will happen, it will update the label text.
Dragging a view onto the tableView creates a .tableHeaderView. You could use a UIContainerView holding the view from another view controller, but unless you intend to have a lot going on that would need to be handled that way, you're better off using a simple UIView.
Try it step-by-step:
1) Standard UITableViewController, with your code ready to go (assign the custom class of the controller to TestTableViewController):
2) Add a UIView to the table view. Give it a background color to make it easy to see:
3) Add a UILabel to that view. Give it a background color to make it easy to see. Constrain it 8-pts on all 4 sides:
4) Set the Custom Class of that view to your ErrorView class:
5) Connect the #IBOutlets. Click-drag (not Ctrl) from the circle in the code window to the object. You may find it easier to drag to the object in the Outline pane (so you don't accidentally connect the Label when you're trying to connect the View, for example):
6) It should look like this now (filled-in circles in the code window indicate the outlets have been connected):
7) Run the app, and you should get this:

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.

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

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.

iOS using xib + global methods for overlay UIView (Swift)

I'm writing an app that should present overlays in specific situations, like for example the lack of location services enabled for the app.
Overlay is a UIView with a UIImageView (background) a UILabel (title) and a UIButton calling a specific action. I want to use Interface Builder to set up the overlay UI but I would like to recall the overlay and show it on different UIViewControllers, depending on when the lack of location services is detected.
I have set up a custom class (subclass of UIView) to link a xib file. Code below:
class LaunchCustomScreen: UIView
{
#IBOutlet var title: UILabel!
#IBOutlet var enableLocationButton: UIButton!
#IBOutlet var waitingIndicator: UIActivityIndicatorView!
#IBOutlet var bckgroundImage: UIImageView!
func setupDefault()
{
title.text = "Location Services Required"
enableLocationButton.setTitle("Enable Location Services", forState: UIControlState.Normal)
enableLocationButton.addTarget(self,
action: "promptUserForLocation",
forControlEvents: UIControlEvents.TouchUpInside)
hideLocButton()
}
func hideLocButton()
{
enableLocationButton.hidden = true
}
func showLocButton()
{
enableLocationButton.hidden = false
}
}
Then I have created the xib file which is of Class LaunchCustomScreen and I linked the IBOutlets to all the objects in it (UILabels, UIBUtton, UIImageView)
Then I have set some global functions to be called from any other UIViewController in order to show/hide the overlay on the specific view controller and configure it with UIButton hidden or visible (it will be hidden with a waiting indicator when user location is still loading). Below related code:
func setupLaunchDefault(vc: UIViewController) -> LaunchCustomScreen
{
for aSubview in vc.view.subviews
{
if aSubview.isKindOfClass(LaunchCustomScreen)
{
NSLog("Found already a launch screen. Removing")
aSubview.removeFromSuperview()
}
}
var screen: LaunchCustomScreen = LaunchCustomScreen()
screen.setupDefault()
return screen
}
func showLaunchAskLocation(vc:UIViewController)
{
var screen = setupLaunchDefault(vc)
screen.bounds = vc.view.bounds
screen.showLocButton()
vc.view.addSubview(screen)
}
Now I'm trying if the solution works and it crashes on the setupLaunchDefault function. Reason is that even if an instance of LaunchCustomSCreen is created, the variables (title, enableLocationButton) are still nil. I though they should be non-nil thanks to the IBOutlet to the xib... what am I missing?
Thank you in advance for your help!
I have set up a custom class (subclass of UIView) to link a xib file
No, you haven't. No such "link" is possible.
what am I missing?
You're not missing anything, because you've already figured it out!
Merely creating a LaunchCustomScreen instance out of thin air (i.e. by saying LaunchCustomScreen(), as you are doing) merely creates an instance of this class. It has nothing whatever to do with the .xib (nib) file! There is no magic "link" whatever between the class and the nib! Thus, nothing happens that would cause these properties to get any value. They are, as you have rightly explained, nil.
You have designed and configured one special particular instance of LaunchCustomScreen in the nib. That is the instance whose outlets are hooked up, within the same nib. So if you want an instance of LaunchCustomScreen with hooked-up outlets, you must load the nib! Loading the nib is exactly equivalent to making an instance of what's in the nib - it is a form of instantiation. And here, it's the form of instantiation you want, because this instance is the instance you want.
So, the answer is: do not say LaunchCustomScreen() to get your LaunchCustomScreen instance (screen). Instead, load the nib to get your LaunchCustomScreen instance - and all will be well.
So, let's say your .xib file is called LaunchCustomScreen.xib. You would say:
let arr = NSBundle.mainBundle().loadNibNamed("LaunchCustomScreen", owner: nil, options: nil)
let screen = arr[0] as UIView
The first result, arr, is an array of top-level objects instantiated from the nib. The first of those objects (probably the only member of the array) is the view you are after! So you cast it to a UIView and you are ready to stick it into your interface. Since the view comes from the nib, its outlets are set, which is what you're after. You can do this as many times as you need to, to get as many "copies" of this view as you like.

Resources