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
}()
Related
I have created my own property wrapper for the theming of UI components like UIView, UILabel etc.
class MyUIViewController: UIViewController {
#Theme private override var view: UIView! // it doesnt work!!!
#Theme private var myCustomView: UIView! // it works!!
}
in this case, i will get a compile error "Cannot override with a stored property 'view'"
I know that the view is a property of UIViewController. Do you know if there is any possible way to apply the property wrapper to a stored(superclass) property? any suggestions would be appreciated :) thanks a lot!
I found a way to do that but it's more like a hack than a good implementation (so I would not recommend it), and I haven't fully tested it (as it really on the UIViewController view loading mechanism, this can lead to some undefined behavior).
That said, in the property wrapper documentation you can find a "translation example" that explains how property wrapper works.
#Lazy var foo = 1738
// translates to:
private var _foo: Lazy<Int> = Lazy<Int>(wrappedValue: 1738)
var foo: Int {
get { return _foo.wrappedValue }
set { _foo.wrappedValue = newValue }
}
So we can imitate this to manually wrap a superclass property.
Note that doing this on the view property is a bit special as the view is not loaded during the view controller initialization, but more like a lazy var.
#propertyWrapper
struct Theme<WrappedValue: UIView> {
var wrappedValue: WrappedValue?
}
class Controller: UIViewController {
override func loadView() {
super.loadView()
_view.wrappedValue = view
}
private var _view: Theme<UIView> = .init()
override var view: UIView! {
get {
if _view.wrappedValue == nil {
// This is a trick I would not recommend using, but basically this line
// forces the UIViewController to load its view and trigger the
// loadView() method.
_ = super.view
}
return _view.wrappedValue
}
set {
_view.wrappedValue = newValue
}
}
}
I made the wrapped value in the property wrapper optional because the view property is nil during the initialization process (as the view is not yet loaded)
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.
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.
I want to add UIView subclass property with lazy initialization, for example:
import UIKit
class MyView: UIView {}
class Controller: UIViewController {
lazy var myView = MyView()
}
But I have an error:
Cannot convert values type 'UIView' to specified type 'MyView'
I can fix the error with type of property:
lazy var myView: MyView = MyView()
or change initialization to:
let myView = MyView()
but why Swift cannot inference the type?
The important thing is providing a type if you are an initialized to a variable marked lazy.
lazy var myView:MyView = MyView()
I tried to replicate the issue but with custom class. And did not found any issues.
One thing to be noted is, when the lazy property had no customisations (the defaultValue in sample) compiler did not asked me to provide the explicit type.
But
For the property with customisation (redView), I had to provide the explicit type.
If I did not provide explicit type here is what I got.
Unable to infer complex closure return type; add explicit type to
disambiguate
And this says clearly enough, that the closure's return type cannot be inferred. Its seems obvious because the closure we are using has no explicit return type.
So I tried to supply a closure with explicit type and I was expecting now that now I would not need to provide the explicit type for the redView lazy property. And as expected, it worked without supplying the type for the lazy property.
if you provide init for MyView, then it will be ok.but why? I spent hours to figure out, the result is ðŸ˜, waiting for the master to answer.
class MyView: UIView {
init() {
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I have to fix another developer's code by adding a dimmedView. I'd like to create this all in code. I understand the ! for an optional value but how would I declare the following view. It doesn't seem like I would need to initialize it but the compiler wants it initialized. How would I deal with this?
class EKViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var mainTV: UITableView!
var dimmedView:UIView
....
func renderOverlayNav(){
println("YYY about to render outlay")
self.dimmedView=UIView(frame: CGRectMake(0.0, 0.0, self.screenWidth, self.screenHeight))
Any instance variables declared within a class have to either be optional, have an initial value, or instantiated inside the init method.
var dimmedView = UIView()
or
var dimmedView: UIView?
or
init() {
dimmedView = UIView()
}
You must initialize all properties in Swift in your init. To get around this you can make the dimmedView an optional which will set it's value to nil or you can initialize it like so var dimmedView = UIView()