Trying to give UIButton a special property - ios

I need UIButton to have the property bookId. I tried the following code but it's giving me the error Property 'self.bookId' not initialized at super.init call. I need the property to be able to query the database for that specific bookId when the button is clicked on.
import UIKit
class BookUIButton: UIButton {
var bookId: String
init(frame: CGRect, bookId: String) {
super.init(frame: frame);
self.bookId = bookId
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func awakeFromNib() {
super.awakeFromNib()
//TODO: Code for our button
}
}
Any help would be greatly appreciated!!!

swift enforces you to initialize every member var before it is ever/might ever be used. Since it can't be sure what happens when it is supers turn, it errors out: better safe than sorry!
Solution 1:
class BookUIButton: UIButton {
var bookId: String?
init(frame: CGRect, bookId: String) {
super.init(frame: frame);
self.bookId = bookId
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func awakeFromNib() {
super.awakeFromNib()
//TODO: Code for our button
}
}
Solution 2:
There is a better way to skip this error. So all you have to do is to initialize member after declaration:
class BookUIButton: UIButton {
var bookId: String = String()
init(frame: CGRect, bookId: String) {
super.init(frame: frame);
self.bookId = bookId
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func awakeFromNib() {
super.awakeFromNib()
//TODO: Code for our button
}
}

Is it important to have it named bookId? And is it important to be a string?
What if you just gave the UIButton a tag? (e.g. ButtonName.tag = Int)

Since you can always instantiate a button from storyboard, that button need to have a required init?(coder aDecoder: NSCoder). That method is required just for you to insert a button in the storyboard.
If the button is actually created via storyboard, than of course it won't have the id soon enough (at init time) for it not to be optional.
Since I assume that you will always instantiate the button programmatically, and not via storyboard, you can just implement your own init, with your params, and than, in the required init, just insert a fatalError.
That will compile as the fatalError returns Never (meaning it never returns), so the compiler can understand that bookId will never actually be nil.
Actually if you delete the required init Xcode will suggest you to insert the method with a fatalError and will autocomplete it for you if you want.
Of course, after that, if you put a button of this class in the storyboard, it will crash, so don't do that.
Adding a property to a button (or view in general) and force it to be created via some init and not via storyboard, in general, is perfectly fine if you don't intend to use the storyboard for that button (or view).
PS: after this brief on how to do what you want to do, I want to suggest you NOT to do it in THIS case, as it seems a bad idea for a button to hold any sort of data about some API you need to call. The button should just be a button and inform you when the user taps it. Then, when the button is pressed, some other class should handle what to do, using some other model to get the correspondent bookId for that button tap.
Completely different was if you would add some property that helped the button to look different, or add some other behavior specific to the use of the button (which is just be tappable and inform of taps)

Related

Convenience Init For UICollectionViewCell

I have a custom UICollectionViewCell that I use in two places throughout my project.
Both UICollectionViewCell's are the same apart from showing a UIButton. To reduce duplication of code I want to use the cell in both places but initialize one with a Boolean that determines if the button is shown or not.
I believe I need a convenience initializer to do this, however, I am getting the error;
'self' used before 'self.init' call or assignment to 'self'
Code:
class MediaSelectionCell: UICollectionViewCell {
var withDeleteButton = false
convenience init(showsDeleteButton: Bool) {
self.init(showsDeleteButton: withDeleteButton)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
How can I resolve this?
Your collectionview cell initialization doesn't have a methods called self.init(showsDeleteButton: withDeleteButton) that why you are getting an error message.
As said in the comment, cells are reuseable. If you register cell with storyboard , required init?(coder: NSCoder) initialization methods called , If you register cell programatically override init(frame: CGRect) is called.
So I mean, If you use dequeueReusableCell you can not change the initialization method by hands.
I prefer to create a two classes to do what you want:
One for not showing button:
class MediaSelectionCell: UICollectionViewCell {
var withDeleteButton = false
override init(frame: CGRect) {
super.init(frame: frame)
// maybe adding constraint your bla bla
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func controlButton() -> Bool{
if withDeleteButton{
// show
return true
}else{
// hide
return false
}
}
}
One for showing button :
class MediaSelectionShowButton : MediaSelectionCell{
override init(frame: CGRect) {
super.init(frame: frame)
self.withDeleteButton = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And in your cell you can control and do what you want with it :
cell.controlButton()
You can’t use a convenience initializer for table view or collection view cells because the table view/collection view creates them by calling the designated initializer.
You have to add a property to your custom class and set it up to honor that property.

SWIFT passing parameter from VC to UIView drives me nuts

I am trying to get this done since yesterday, but no approach was successful. While I have learned a lot, some fundamentals still seem to be missing.
I have a view controller that shall contain several subviews in a stack. Each subview shall use the same UIView class in a separate swift file. However, for each subview I want to pass a position ID to the UIView class. The controller and the subviews are created with the storyboard.
So my latest and best approach is
class SpatialViewController: UIViewController {
#IBOutlet var redSquare: SpatialProblemView!
#IBOutlet var blueSquare: SpatialProblemView!
override func viewDidLoad() {
super.viewDidLoad()
redSquare = SpatialProblemView(subviewName: "red")
blueSquare = SpatialProblemView(subviewName: "blue")
// redSquare.subviewName = "red"
self.view.addSubview(redSquare)
}
}
and
class SpatialProblemView: UIView {
var subviewName: String
init(subviewName: String){
self.subviewName = subviewName
super.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I have also tried to code the UIView with this
required init(subviewName: String) {
super.init(frame: .zero)
self.subviewName = subviewName
self.setup()
}
required override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
func setup() {
if subviewName == "red" {
print ("red")
}
}
but that seem further apart from a running code. With the upper UIView definition I receive the error Fatal error: init(coder:) has not been implemented in the console and Property 'self.subviewName' not initialized at super.init call inline within the editor. Both relate to
super.init(coder: aDecoder)
I have looked for other posts here with this error message, but none I saw helped me. Any help is highly appreciated :-)
Try to make it optional
var subviewName: String?
and if you make them as outlets then you shouldn't assign a new instance here
SpatialProblemView(subviewName: "red")
just assign the property
redSquare.subviewName = "red"

How to create a UIView singleton in swift?

I want to avoid create UIView like This:
let singleView = SingleView()
Here is How I create a SingleTon:
class SingleView:UIView {
static let sharedInstance = SingleView()
private init() {
super.init(frame:CGRectZero)
}
override init(frame:CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
However it will compile sucess.
First of all, a view singleton is a bad idea, because a UIView instance will only have one superView, e.g. be subView of only one other view.
If you use this only once, there is no benefit from sharing the instance.
If you use it in different UIViewController instances, it will probably not behave as you expect.
Anyhow, you seem to not even want a singleton, which is good.
So, how about this?
class SingleView:UIView {
init() {
super.init(frame:CGRectZero)
}
override init(frame:CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Now you can initialize without parameters in the constructor like you wanted all along.
PS: please note that constructors in swift always create new instances (or none at all). If you want to use a singleton, you hide the constructors and provide a factory-method (or a static let as you did).

Save styled element to reuse in other view controllers

I'm making an iOS app with Xcode where all my buttons should have the same style. The only difference between these buttons are their height and width. Is there a way to save the first one I styled, and then use it again in the different view controllers, without copying? I'm thinking if this is possible it'll save me a lot of time.
Applying the same style to multiple instances of UIButton:
Strictly Programmatic route:
The first two methods are what I would do. The third is only to illustrate that it is possible to write an init that copies settings from another button.
Apply preset style with a sub class:
class StyledButton : UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.blackColor()
// more styling
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Apply preset style with an extension to UIButton:
extension UIButton {
func setCustomStyle1() {
self.backgroundColor = UIColor.blackColor()
// nore styling
}
}
Copy style with a convenience init in an extension to UIButton:
extension UIButton {
convenience init(styleFromButton button: UIButton, frame: CGRect) {
self.init(frame: frame)
self.backgroundColor = button.backgroundColor
}
}
Interface Builder solution:
Create a new Swift file:
Create a sub class of UIButton in the new file:
class StyledButton : UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
style()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
style()
}
private func style() {
self.backgroundColor = UIColor.blackColor()
// more styling
}
}
Go back to the Interface Builder and select a UIButton you want to style.
Select the third panel on the right, this is the identity inspector.
Select your sub class as the class for the UIButton.
Repeat for all buttons to style.
Or style the entire thing in IB and Alt-Drag to make a copy.

How to implement two inits with same content without code duplication in Swift?

Assume a class that is derived from UIView as follows:
class MyView: UIView {
var myImageView: UIImageView
init(frame: CGRect) {
super.init(frame: frame)
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
...
If I wanted to have the same code in both of the initializers, like
self.myImageView = UIImageView(frame: CGRectZero)
self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill
and NOT duplicate that code twice in the class implementation, how would I structure the init methods?
Tried approaches:
Created a method func commonInit() that is called after super.init -> Swift compiler gives an error about an uninitialized variable myImageView before calling super.init
Calling func commonInit() before super.init fails self-evidently with a compiler error "'self' used before super.init call"
What we need is a common place to put our initialization code before calling any superclass's initializers, so what I currently using, shown in a code below. (It also cover the case of interdependence among defaults and keep them constant.)
import UIKit
class MyView: UIView {
let value1: Int
let value2: Int
enum InitMethod {
case coder(NSCoder)
case frame(CGRect)
}
override convenience init(frame: CGRect) {
self.init(.frame(frame))!
}
required convenience init?(coder aDecoder: NSCoder) {
self.init(.coder(aDecoder))
}
private init?(_ initMethod: InitMethod) {
value1 = 1
value2 = value1 * 2 //interdependence among defaults
switch initMethod {
case let .coder(coder): super.init(coder: coder)
case let .frame(frame): super.init(frame: frame)
}
}
}
I just had the same problem.
As GoZoner said, marking your variables as optional will work. It's not a very elegant way because you then have to unwrap the value each time you want to access it.
I will file an enhancement request with Apple, maybe we could get something like a "beforeInit" method that is called before every init where we can assign the variables so we don't have to use optional vars.
Until then, I will just put all assignments into a commonInit method which is called from the dedicated initialisers. E.g.:
class GradientView: UIView {
var gradientLayer: CAGradientLayer? // marked as optional, so it does not have to be assigned before super.init
func commonInit() {
gradientLayer = CAGradientLayer()
gradientLayer!.frame = self.bounds
// more setup
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
commonInit()
}
init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer!.frame = self.bounds // unwrap explicitly because the var is marked optional
}
}
Thanks to David I had a look at the book again and I found something which might be helpful for our deduplication efforts without having to use the optional variable hack. One can use a closure to initialize a variable.
Setting a Default Property Value with a Closure or Function
If a stored property’s default value requires some customization or setup, you can use a closure or global function to provide a customized default value for that property. Whenever a new instance of the type that the property belongs to is initialized, the closure or function is called, and its return value is assigned as the property’s default value. These kinds of closures or functions typically create a temporary value of the same type as the property, tailor that value to represent the desired initial state, and then return that temporary value to be used as the property’s default value.
Here’s a skeleton outline of how a closure can be used to provide a default property value:
class SomeClass {
let someProperty: SomeType = {
// create a default value for someProperty inside this closure
// someValue must be of the same type as SomeType
return someValue
}()
}
Note that the closure’s end curly brace is followed by an empty pair of parentheses. This tells Swift to execute the closure immediately. If you omit these parentheses, you are trying to assign the closure itself to the property, and not the return value of the closure.
NOTE
If you use a closure to initialize a property, remember that the rest of the instance has not yet been initialized at the point that the closure is executed. This means that you cannot access any other property values from within your closure, even if those properties have default values. You also cannot use the implicit self property, or call any of the instance’s methods.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/de/jEUH0.l
This is the way I will use from now on, because it does not circumvent the useful feature of not allowing nil on variables. For my example it'll look like this:
class GradientView: UIView {
var gradientLayer: CAGradientLayer = {
return CAGradientLayer()
}()
func commonInit() {
gradientLayer.frame = self.bounds
/* more setup */
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
commonInit()
}
init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
}
How about this?
public class MyView : UIView
{
var myImageView: UIImageView = UIImageView()
private func setup()
{
myImageView.contentMode = UIViewContentMode.ScaleAspectFill
}
override public init(frame: CGRect)
{
super.init(frame: frame)
setup()
}
required public init(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
setup()
}
}
Does it necessarily have to come before? I think this is one of the things implicitly unwrapped optionals can be used for:
class MyView: UIView {
var myImageView: UIImageView!
init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
self.commonInit()
}
func commonInit() {
self.myImageView = UIImageView(frame: CGRectZero)
self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill
}
...
}
Implicitly unwrapped optionals allow you skip variable assignment before you call super. However, you can still access them like normal variables:
var image: UIImageView = self.myImageView // no error
Yet another option using a static method (added 'otherView' to highlight scalability)
class MyView: UIView {
var myImageView: UIImageView
var otherView: UIView
override init(frame: CGRect) {
(myImageView,otherView) = MyView.commonInit()
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
(myImageView, otherView) = MyView.commonInit()
super.init(coder: aDecoder)!
}
private static func commonInit() -> (UIImageView, UIView) {
//do whatever initialization stuff is required here
let someImageView = UIImageView(frame: CGRectZero)
someImageView.contentMode = UIViewContentMode.ScaleAspectFill
let someView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
return (someImageView, someView)
}
}
Assign myImageView in both the init() methods based on a single image create function. As such:
self.myImageView = self.createMyImageView ();
For example, like such:
class Bar : Foo {
var x : Int?
func createX () -> Int { return 1 }
init () {
super.init ()
self.x = self.createX ()
}
}
Note the 'optional' use at Int?
Additionally, if the intention is to assign myImageView exactly once, it should be a let rather than a var. That rules out some solutions that only work for var.
Another complication is multiple instance variables with dependencies between them. This rules out inline initializers calling static methods.
These requirements can be addressed by overriding with convenience initializers, which delegate to a single designated initializer:
import UIKit
class MyView: UIView {
let myImageView: UIImageView
// Just to illustrate dependencies...
let myContainerView: UIView
override convenience init(frame: CGRect) {
self.init(frame: frame, coder: nil)!
}
required convenience init?(coder aDecoder: NSCoder) {
// Dummy value for `frame`
self.init(frame: CGRect(), coder: aDecoder)
}
#objc private init?(frame: CGRect, coder aDecoder: NSCoder?) {
// All `let`s must be assigned before
// calling `super.init`...
myImageView = UIImageView(frame: CGRect.zero)
myImageView.contentMode = .scaleAspectFill
// Just to illustrate dependencies...
myContainerView = UIView()
myContainerView.addSubview(myImageView)
if let aDecoderNonNil = aDecoder {
super.init(coder: aDecoderNonNil)
} else {
super.init(frame: frame)
}
// After calling `super.init`, can safely reference
// `self` for more common setup...
self.someMethod()
}
...
}
This is based on ylin0x81's answer, which I really like but doesn't work now (build with Xcode 10.2), as load from nib crashes with:
This coder requires that replaced objects be returned from initWithCoder:
This issue is covered on a separate question, with iuriimoz's answer suggesting to add #objc to the designated initializer. That entailed avoiding the Swift-only enum used by ylin0x81.

Resources