Initialize instance variables inside instance function - swift

final class TestVC: UIViewController {
var usersFooter: Footer!
var groupsFooter: Footer!
override func viewDidLoad() {
super.viewDidLoad()
bind(footer: &usersFooter)
}
func bind(footer: inout Footer) {
footer = Footer(style: .autoFooter, height: 57) {
// doing something
}
}
}
Thats what Footer is:
final class Footer: RefreshView {
private var loader: MDCActivityIndicator!
override public init(style: Style, height: CGFloat, action: #escaping () -> Void) {
// initializing and doing something with loader
super.init(style: style, height: height, action: action)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I get this:
Cannot pass immutable value of type 'Footer' as inout argument
How to pass TestVC instance's footers in it's function and be able to initialize them?
Why is footer that immutable (Declared as var)?

This occurs because
var someVar: Footer!
does not define a variable of type Footer but a variable of type Optional<Footer> that is implicitly unwrapped. The code:
var someFooter: Footer!
bind(footer: &someFooter)
is logically equivalent to
var someFooter: Footer?
guard let tempFooter = someFooter? else { fatalError() }
bind(footer: &tempFooter)
As you can see, tempFooter is a let, so it can't be passed as an inout variable and even if it could, the result would be thrown away.
You can fix this in one of three ways:
make the parameter to bind an optional, e.g. func bind(footer: inout Footer?) or use Martin's syntax to make it implicitly optional.
Force the unwrap yourself:
var unwrapped: Footer = someFooter
bind(footer: unwrapped)
someFooter = unwrapped
redesign the API. It seems the first thing you do in the bind function is overwrite the old footer with a newly initialised footer. So don't use an inout parameter, return the value you want i.e.
func bind() -> Footer
{
var theFooter = Footer(...) { ... }
// do stuff
return theFooter
}
someFooter = bind()
I think the last option is the best in this case.

Write bind method like this. It will resolve your error.
func bind(footer: inout Footer!) {
footer = Footer(style: .autoFooter, height: 57) {
// doing something
}
}
It seems like inout thinks Footer & Footer! are different. Either update method as above or change declaration as given below.
var userFooter: Footer
I don't know the exact reason but the error which we are getting is confusing.

Related

Swift: Convert object instance name to String for use as key

Given var varName = "varValue", is there a way in Swift to convert the variable name to String at runtime? For example in this case I would get back "varName".
I already know about Mirror APIs for reflection in Swift. That allows me to iterate over the properties of a given class but I would like to apply this to self for any given class. I want to use this to generate String keys automatically for any given object (irrespective of which class it belongs to)
extension UIView {
var key: String {
return "" //TODO: Convert self to varName as String
}
}
// Usage
let customView = UIView()
customView.key // should be "customView"
Update:
The OP added this comment clarifying the requirements:
I need the 'key' to be different for 2 instances of UIView. I want that key to be the same every time for that particular instance i.e. the key shouldn't change if the app is restarted or the view instance is destroyed and recreated. I can use this key as key in caching. Another use case can be to use it as accessibilityIdentifier to help with UITesting.
In that case, I suggest to not even think about using ✨magic✨. Just explicitly give your view instances an identifier. You could also totally just reuse existing properties on UIView like tag or accessibilityIdentifier. If that's not enough or not convenient enough, subclass:
class IdentifiableView: UIView {
public private(set) var identifier: String
init(frame: CGRect, identifier: String) {
self.identifier = identifier
super.init(frame: frame)
self.accessibilityIdentifier = identifier
}
init() {
fatalError("Must use init(frame:identifier:)")
}
override init(frame: CGRect) {
fatalError("Must use init(frame:identifier:)")
}
required init?(coder aDecoder: NSCoder) {
fatalError("Must use init(frame:identifier:)")
}
}
// Usage
let firstView = IdentifiableView(frame: .zero, identifier: "First View")
firstView.identifier
firstView.identifier
let otherView = IdentifiableView(frame: .zero, identifier: "Second View")
otherView.identifier
otherView.identifier
If, according to your comment, you simply want "objects to return a unique key that does not change", you could simply use their memory address:
extension UIView {
var key: String {
return "\(Unmanaged.passUnretained(self).toOpaque())"
}
}
let firstView = UIView()
firstView.key // -> "0x00007fbc29d02f10"
firstView.key // -> "0x00007fbc29d02f10"
let otherView = UIView()
otherView.key // -> "0x00007fbc29d06920"
otherView.key // -> "0x00007fbc29d06920"
For each instance of UIView you create this will return a unique value that will not change.
I'm not sure what you are trying to accomplish here but I think you can convert variable name to string in Swift with #keyPath(propertyName) but it requires you to add #objc to your var.
For example
class MyViewController : UIViewController {
#objc var name: String = "John"
}
print(#keyPath(MyViewController.name))
prints name in the console.

Swift: Generic Protocol Cannot invoke with an argument list of type

Trying to create a generic protocol with associatedtype.
I get an error when I try to access a method from the delegate:
Cannot invoke 'numberOfSections' with an argument list of type '(containerView: UITableView)'
Code:
protocol ViewDelegate: class {
associatedtype ContainerView
associatedtype Model
func numberOfSections(containerView: ContainerView)
func aMethodThatTakesNoArugments()
}
class ViewController: UIViewController {
var newView = AnyView<ViewController>()
override func viewDidLoad() {
super.viewDidLoad()
newView.delegate = self
}
}
extension ViewController: ViewDelegate {
typealias ContainerView = UITableView
typealias Model = Int
func numberOfSections(containerView: ContainerView) {
// do something with containerView
}
func aMethodThatTakesNoArugments() {}
}
class AnyView<Delegate: ViewDelegate>: UIView {
weak var delegate: Delegate?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func getData() {
delegate?.aMethodThatTakesNoArugments() // This compiles fine
delegate?.numberOfSections(containerView: UITableView()) // Get a compiler error on this line (I am passing an argument):
// Cannot invoke 'numberOfSections' with an argument list of type '(containerView: UITableView)'
}
}
I have a feeling I am missing something. Method that takes no arguments compiles fine; however if I call a method that does take an arugment, I get a compile error.
Your error is due to the compiler not being able to tell what Type ContainerView is supposed to be.
You're only defining it for ViewController, but the Delegate could be literally any class or struct, not just ViewController.
There are a number of ways to fix this, but it's unclear what exactly you're trying to accomplish here, so I'll just give a couple examples:
as #OOPer mentioned, you could constraint your Delegate generic to force conformance to UITableView:
class AnyView<Delegate: ViewDelegate>: UIView where Delegate.ContainerView == UITableView
A similar option would be to simply define the protocol with the desired type:
protocol ViewDelegate: class {
func numberOfSections(containerView: ContainerView)
func aMethodThatTakesNoArugments()
}
If you need more flexibility, another option would be to add another generic Type:
class AnyView<Delegate: ViewDelegate, ContainerViewType>: UIView where Delegate.ContainerView == ContainerViewType

What does this mean: "You do not have to provide an explicit implementation of a required initializer if you can satisfy the requirement"?

I've found this note in the Swift documentation about initializers:
You do not have to provide an explicit implementation of a required initializer if you can satisfy the requirement with an inherited initializer.
What is an "explicit" implementation? What is an "implicit" one then?
What does "satisfy the requirement with an inherited initializer" mean precisely?
Could you give me a code example, in which I don't have to provide an explicit implementation of a required initializer?
Here's an example with an inline explanation:
protocol JSONInitializable { // Use Encoders, but just for example
init(fromJSON: String)
}
class Foo: JSONInitializable {
let x: Int
// "required" is necessary because this init is required for the
// conformance to JSONInitializable
required init(fromJSON json: String) {
//...
x = 123 //some value from the JSON
}
}
class Baz: Foo {
// `init(fromJSON json: String)` can be inherited,
// so it's implicitly defined for Baz, as well as Foo.
}
class Bar: Foo {
// The presence of this uninitialized constant `y` requires an
// a value in the declaration, or an initializer that sets it
let y: Int
// Since we didn't specify a value for `y` in its declaration,
// this initializer must be explicitly specified so as to initialize `y`.
// Doing so blocks the inheritance of `init(fromJSON json: String)` from
// the super class, and requires us to define it ourselves,
// in order to preserve conformance to `JSONInitializable`
required init(fromJSON json: String) {
//...
y = 0
super.init(fromJSON: json)
}
}
It is saying this: If you have initialized all your properties as you declare them, there's no need to write an initializer.
Normally, when you have properties declared and not set, you write an init() method and set them there, and if there's a required initializer in the parent class, you call
super.init(possible, arg: anotherArg)
Since you don't need to set anything, you don't need to write anything, and since there's no init in your class, the super call will happen automatically. (Of course, if the required init needs values passed in, you still need to supply them.) So how is that accomplished? (see below.) The bottom line is that most of the work is done for you.
However, once you write an init(), then you're taking away this automatic behavior, and you have to make sure that those "automatic" calls are explicitly made.
Finally, before I provide an example, I should address this statement:
"if you can satisfy the requirement with an inherited initializer"
If the parent doesn't have an initializer which takes no arguments, how will it get those arguments? In that case you need initialize your class using that required super's init with the proper arguments.
Here's an example of a view controller which is not set up in IB, I would normally create it using the super's required initializer, since I didn't write an initializer for my derived class:
let vc = MagicController(nibName: nil, bundle: nil)
import UIKit
import Dotzu
class MagicController: UIViewController, UIGestureRecognizerDelegate {
let action = #selector(MagicController.buttonTapped(_:))
let action2 = #selector(MagicController.secondButtonTapped(_:))
let mainAction = #selector(MagicController.mainButtonTapped(_:))
let defaultPalette = Palette.randomPalette()
var questions = [Question]()
var startTime = TimeInterval()
var endTime = TimeInterval()
var selectedTimeIndex = 0
var selectedTagIndex = 0
var selectedRatingIndex = 0
var selectedTag = "swift"
let timeSpanDropDown = DropDown()
let ratingDropDown = DropDown()
let tagDropDown = DropDown()
var pageNumber = 1
var savedIndex = 0
var savedPostId = -1
var quotaCount = -1
var isFirstTime = true
let queryFactory = Queries(client: APIClient())
var timeSpanButton = UIBarButtonItem()
var ratingButton = UIBarButtonItem()
var tagButton = UIBarButtonItem()
var dbButton = UIBarButtonItem() // 🗄
/// start the console log, configure the main view's buttons/actions and present the UI
override func viewDidLoad() {
super.viewDidLoad()
Dotzu.sharedManager.enable()
self.edgesForExtendedLayout = []
configureButtons(container: view)
view.setNeedsLayout()
}
///configure the buttons/actions, prepare and present the UI
/// The first time this is called it sets up the buttons and drop downs
/// cleanupDataSource() has no effect the first time it is called
/// - Parameter animated: passed to super
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isFirstTime {
isFirstTime = false
initializeButtons()
setupDropDowns()
}
cleanupDataSource()
}
/// etc.
}
As you can see, I did not write an init for this view controller, even though a view controller has a required init. I initialized my view controller using an init() that isn't even in my code.

uiview as optional parameters in swift

I have a function where I am passing the UIStackview as a parameter. I want that parameter to be optional.
func render(parentview: UIStackView = nil) {
//
}
how can I make this function optional and how to give a default value to it?
You can declare method like below:
func test(_ myNumber: Int? = 2) {
}
In above function, I have provided a default value as 2.
I can call the function as
test()
or
test(5)

Protocol being reset in cell

I have a cell with a textField and a button. The button opens a page to collect data and has a protocol to pass that data back to the cell and fill the textField. That all works fine, however, when I come back the value is reset to zero. Print statements show that it is passing the data when it's set, but hitting Back clears it for some reason.
Protocol
protocol DistanceProtocol {
func distanceSet(distance: Double)
}
Call to protocol method
distanceProtocol?.distanceSet(totalDistance)
Cell class
class InputCell: CalculatorCell, DistanceProtocol {
#IBOutlet var textField: UITextField?
private var inputType = InputType.undefined
var viewController = UIViewController()
override func getHeight() -> CGFloat {
return 90
}
func distanceSet(distance: Double) {
print(distance)
textField?.text = "\(distance)"
}
func getInputType() -> InputType {
return inputType
}
func setInputType(inputType: InputType) {
self.inputType = inputType
}
#IBAction func walkTouched(sender: UIButton) {
let mapVc = viewController.storyboard!.instantiateViewControllerWithIdentifier("Map") as! MapLocationsViewController
mapVc.distanceProtocol = self
viewController.navigationController?.pushViewController(mapVc, animated: true)
}
}
As far as I can tell, everything is set up correctly. It's not reloading the cells in the tableView when I come back. Why is it resetting/how can I prevent it?
the text field is only set when you call the function. Unless you call this function in cellForRowAtIndex path it wont retain the value
I would likely implement it using a setter, do everytime the value is set, the label gets updated
protocol DistanceProtocol {
func distanceSet(distance: Double)
}
class CellWithText: UITableViewCell {
var cellText: String {
didSet {
textLabel?.text = cellText
}
}
}
extension CellWithText: DistanceProtocol {
func distanceSet(distance: Double) {
self.cellText = "\(distance)"
}
}
Then in your cellForRowAtIndexPath call you would call the function
cell.distanceSet(19.0)
i think you might try singleton design pattern or you can use struct for copying.