Swift: lazy instantiation of IBOutlet var UITableView - ios

I'm trying to do an lazy instantiation of IBOutlet var UITableView:
#IBOutlet lazy weak var tableView: UITableView? = {
return UITableView()
}()
But I'm getting the following errors:
<unknown>:0: error: cannot convert return expression of type 'UITableView?' to return type 'UITableView?'
<unknown>:0: error: cannot assign value of type 'UITableView?' to type 'UITableView??'
<unknown>:0: error: cannot assign value of type 'UITableView?' to type 'UITableView??'
Why I'm getting this error?
In this other case works just fine:
lazy var viewController: ViewController = {
return ViewController()
}()

Here's the right way to connect your ui elements to the Interface builder, take a look, it should help:
https://developer.apple.com/library/content/referencelibrary/GettingStarted/DevelopiOSAppsSwift/ConnectTheUIToCode.html

What you are trying to do is impossible, and kind of senseless.
When you mark a property as an #IBOutlet and connect your code to your .storyboard or .xib, it will look like:
class MyViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
...
}
Notice, that tableView property does not have an initialiser. This is because it will not be initialised during MyViewController construction, but will be assigned later when interface builder will load the view. Also, this will only happen if you instantiate your view controller via storyboard or xib. Doing let viewController = MyViewController() will result in tableView property always being nil (unless you override your constructors or func loadView()).
lazy properties on the other hand MUST have an initialiser, which will be called lazily after you first access the property. Just like you've done in your code. The thing is, when you mark your tableView as lazy and provide your own initialiser for it, in runtime it will create an UITableView instance, which is not connected to your interface builder (.storyboard) in any way.
So you should either not use storyboards and make your layout programmatically (just like you did in your code, but removing #IBOutlet), or let the interface builder instantiate your views for you by removing your initialiser and lazy keyword.

Related

Swift lazy initialization can't conform to the protocol

I declared several UI components in a protocol.
protocol SomeViewContainer {
var aView: ACustomizedView
...
}
class TestViewController: SomeViewContainer {
var aView: ACustomizedView!
}
The above code won't pass because the compiler doesn't think TestViewController conforms to the protocol.
The aView will be initialized after the data fetched from the remote, so I can't just remove the ! .
In addition, I would prefer to lazy initialzation like the following for some other properties declared in the protocol.
lazy var aView: UIView! = {
}()
Still Failed to compile.
Are there any ideas on how to conform a protocol with lazy initialization?
So two issues, one a property with type ACustomizedView! is not the same as a property with ACustomizedView which is why it doesn't conform
Secondly, you should be able to use lazy.
Is that your actual code?
lazy initialization uses a self executing closure, so it'll run the closure code automatically when called and supply the property with what the closure RETURNS
the fact you have nothing inside the closure will cause it to break..
you need to actually return a view from inside the closure
lazy var aView: UIView = {
let view = UIView()
// configure view
return view
}()

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

How to properly set a property in a protocol from a conforming class?

I am writing a custom table header view that can expand/collapse, I wrote a protocol for it like below:
protocol ExpandableHeadViewDelegate{
var expandStateReference : [String : Bool] { get set }
var tblVw : UITableView { get set }
func didTapActionFromHeadVw(_ view: ExpandableHeadView, tag: Int)
}
Here is my expandable head view class, what Im trying to achieve is when this view is tapped I will be able to call the methods that I need from the UITableView where expandableView is embedded:
class ExpandableHeadView: UIView {
var delegate : ExpandableHeadViewDelegate?
//set up tap recognizer
.
.
.
.
private func viewTapped {
if let delegate = delegate {
delegate.tblVw.reloadData()
}
}
}
My viewcontroller that utilizes this class is as below:
class PlayersViewController: UIViewController,UITableViewDelegate,
UITableViewDataSource, ExpandableHeadViewDelegate {
var expandedCells = [String : Bool]() //
#IBOutlet var tableVw: UITableView! // compile time error
}
The issue I am facing is even though I declare my 'var tableVw' in my delegate class, Xcode gives me an error:
Protocol requires property 'tableVw' with type 'UITableView'; do you
want to add stub?
Setting var expandedCells .. however sets properly. If I set my tableVw as non-IBOutlet it can compile, but I want my tableVw to be an IBOutlet property.
EDIT:
Adding a stub variable for tableVw and assigning my tableView IBOutlet to it works. But Im still curious if this can be achieved without using a stub variable
internal var tableVw: UITableView?
#IBOutlet var tableView: UITableView!
.
.
func viewDidload {
self.tableVw = self.tableView
}
Quite simply - declare the property in your protocol as UITableView!. The ! matters - a lot.
! makes a variable an optional, but an explicitly unwrapped one. This basically means, that the unwrapping is done for you, under the hood. You promise the compiler, that although this variable can contain a nil, you will make sure it will be set with a proper value before you try to access it. In case of IBOutlets this is done for you by the SDK.

Swift EXC_BAD_ACCESS when accessing a subclass property

I subclassed UIImageView to add a custom property.
class ProfilePictureImageView: UIImageView {
var isAffirmed: Bool?
}
When trying to set isAffirmed, I get an EXC_BAD_ACCESS error. Here is a stripped down version of my class to show the error:
class SettingsTableViewController: UITableViewController {
#IBOutlet weak var userImageView: ProfilePictureImageView!
override func viewDidLoad() {
super.viewDidLoad()
userImageView.image = nil // This works
userImageView.isAffirmed = true //EXC_BAD_ACCESS error here
}
}
I'm able to access and set properties of UIImageView (such as image), but not properties from my subclass. What is the cause of this error?
Make sure that you have set the custom class for the UIImageView in your storyboard, otherwise you will get a plain old UIImageView and an exception when you access the new property
Sounds like you haven't changed your class from UIImageView to ProfilePictureImageView in Interface Builder.

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().

Resources