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.
Related
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"
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)
I am creating a UIView subclass with the intention to force users to my required init method than the default one.
So for that, I have created a convenience method for this.
#available(*, unavailable, message: "init is unavailable.")
public override init(frame: CGRect) {
super.init(frame: frame)
}
required
convenience public init(withSomeParameters myParam:Type) {
self.init(frame: CGRect.zero)
//Doing something nice!
}
This works! However, when I try to init it's showing me two ways to initialize it. How to force the user to make use of custom init method?
You can make it private , so user must need to init Test class with withSomeParameters
class Test:UIView {
private override init(frame: CGRect) {
super.init(frame: frame)
}
convenience public init(withSomeParameters myParam:Type) {
self.init(frame: CGRect.zero)
//Doing something nice!
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Maybe you need to mark the coder initialiser as unavailable as well:
#available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Plus: remove the convenience from your initialiser, and call supers init(frame:)
public init(withSomeParameters myParam:Type) {
super.init(frame: .zero)
//Doing something nice!
}
As another example, here is some base UIView subclass I use in a lot of my projects that don't utilise storyboards:
class MXView: UIView {
init() {
super.init(frame: .zero)
}
// Storyboards are incompatible with truth and beauty.
#available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Subclass:
class CustomView: MXView {
init(someParameters params: Type) {
// Phase 1: store ivars.
super.init()
// Phase 2: Do something nice.
}
If you do it like that, users of CustomView will be forced to use init(someParamters:). init(frame:) is shadowed because init(someParameters:) is a non-convenience init.
I have a simple UIView subclass that looks like this:
import UIKit
class AngleViewManager: UIView {
class AngleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.red
}
init(first: CGPoint, second: CGPoint, third: CGPoint) {
// setup
super.init(frame: CGRect(dimensions))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}
When I initialize an AngleView instance within AngleViewManager like
AngleView(firstPoint, secondPoint, thirdPoint),
the background color is not red, and setting a breakpoint in the overridden init shows that it is never called even though I am explicitly calling it in my custom initializer which does get called successfully.
Am I missing something obvious?
You're calling the super class's (UIView) implementation of init(frame: CGRect).
Just change it to self.init(frame: frame)
The following code returns a couple of compiler errors after converting to swift3:
override init(frame: CGRect) { //Initializer does not override a designated initializer from its superclass
super.init(frame: frame) //Must call a designated initializer of the superclass 'MKAnnotationView'
}
How do I go about fixing this?
I am guessing (from the comment in your code) that you are trying to create a subclass of MKAnnotationView. If thats true, try this.
class myAnnot : MKAnnotationView{
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}