I have coded a custom UIButton as :
class AccountOpeningButton: UIButton {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
......
}
}
I am able to instantiate this Class successfully using my Storyboard.
Now, i made a UIView & want to add this button in my UIView as :
var customView:UIView = UIView()
customView.frame = CGRect(x: 0, y: 0, width: 350, height: 250)
.....
let fromDateBtn:UIButton = AccountOpeningButton()//Error comes here as : Missing Argument for parameter ‘coder’ in call
customView.addSubview(fromDateBtn)
So please help in in reusing this code dynamically also.
P.S. : I referred http://napora.org/nscoder-and-swift-initialization/
Fatal error: use of unimplemented initializer 'init(coder:)' for class
Class does not implement its superclass's required members
But didn't succeed.
=======================================================================
TRIED
let fromDateBtn:UIButton = UIButton() as! AccountOpeningButton
This throws CastException Could not cast value of type 'UIButton' to '.AccountOpeningButton'
Replace This line
let fromDateBtn:UIButton = AccountOpeningButton()
With This:
let fromDateBtn = AccountOpeningButton()
And add this method in your class
override init(frame: CGRect) {
super.init(frame: frame)
}
You can have more than one init method, but you have to obey the
inheritance and hierarchy rules. And you need to definitely understand
what are called convenience initializers.
For more details find Here
Related
I am new to Swift.
Currently I have made serval xib files and they can be rendered in the following codes
let mySubview:customView = customView(frame: CGRect(x:10,y:300, width: 312, height:355))
self.view.addSubview(mySubview)
"customView" is the custom view (.xib) file while there are many others. However, I want to render it with a function parameter. I have used string but I got an error for this:
func addComp(name:String){
let className = NSClassFromString("MyApp."+name) as! UIView.Type
let subview = className.init()
subview.frame = CGRect(x:10,y:300, width: 312, height:355)
self.view.addSubview(subview)
}
It says
"Fatal error: Unexpectedly found nil while unwrapping an Optional value"
Anyway, is there any ways to define a custom view with function parameters? Either with string or any other methods.
I'd say you're coming at this wrong. Don't turn a string to a type; use a type directly.
Here's a UIView subclass with a factory method that does everything your addComp does:
class MyFactoryView : UIView {
required override init(frame:CGRect) {
super.init(frame:frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
}
static func make(andAddTo v:UIView) {
let subv = self.init(frame:CGRect(x:10,y:300, width: 312, height:355))
v.addSubview(subv)
}
}
Okay, so let's say we have some subclasses of that type:
class MyView1 : MyFactoryView {}
class MyView2 : MyFactoryView {}
So now there's no need for any string. To enact our little drama, we just talk directly to the type; for example:
MyView1.make(andAddTo:self.view)
I created a pair of xib file with the swift file(for that xib file).
[xib_template.xib & view_template.swift]
And I want to control this pair of xib file by my [main_VC.swift].
xib file have 1 button and 1 label.
I want to change the text of label when I click this button.
I want to set different template view and control them in my [main_VC].
But the #IBAction seems independent inside the class
I pass the value from [main_VC] to [view_template.swift] by init method searched on the internet.
I can get correct value by using func in [main_VC].
But when clicking the button,
the value is always nil.
The var inside IBAction cannot get the value from init.
I am new in swift and I tried my best but still cannot fix this.
How can I get the init value inside IBAction?
Or how can I programmatically create & disable the Ibaction from [main_VC]?
I adjusted my code to be more easy to read.
May have some little typing error.
I searched online and tried all I can already.
One people asked similar question before but have no answer.
Hope for help.
Thanks very much.
[view_template.swift]
import UIKit
class View_template_empty: UIView {
var _uid: String?
#IBOutlet weak var labellabel: UILabel!
init (uid: String) {
self._uid = uid
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
}
required init?(coder aDecoder: NSCoder) {
// fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
}
#IBAction func clickingPanel2(_ sender: Any) {
print(self._uid) // always nil !!!!!!
self.labellabel.text = “test”
}
fun test () {
print(self._uid) // correct value
}
}
[main_VC] (only copy out the main function)
func allocator (_uid: String, uiView: UIView) {
switch templateType {
case “one”:
if let loading_panels = Bundle.main.loadNibNamed("xib_template", owner: uiView, options: nil)?.first as? view_template {
loading_panels.translatesAutoresizingMaskIntoConstraints = false
uiView.addSubview(loading_panels)
loading_panels.leadingAnchor.constraint(equalTo: uiView.leadingAnchor).isActive = true
loading_panels.trailingAnchor.constraint(equalTo: uiView.trailingAnchor).isActive = true
loading_panels.bottomAnchor.constraint(equalTo: uiView.bottomAnchor).isActive = true
loading_panels.topAnchor.constraint(equalTo: uiView.topAnchor).isActive = true
let view_temp = view_template(uid: _uid)
view_temp.test()
}
case “two”:
if let loading_panels = Bundle.main.loadNibNamed("xib_template_two”, owner: uiView, options: nil)?.first as? view_template_two {
loading_panels.translatesAutoresizingMaskIntoConstraints = false
uiView.addSubview(loading_panels)
loading_panels.leadingAnchor.constraint(equalTo: uiView.leadingAnchor).isActive = true
loading_panels.trailingAnchor.constraint(equalTo: uiView.trailingAnchor).isActive = true
loading_panels.bottomAnchor.constraint(equalTo: uiView.bottomAnchor).isActive = true
loading_panels.topAnchor.constraint(equalTo: uiView.topAnchor).isActive = true
}
default:
print("error")
}
You are using different initializers here:
When you say let view_temp = view_template(uid: _uid), init (uid: String) is used and your implementation sets _uid so it is not nil.
When you load a view from a XIB, init?(coder aDecoder: NSCoder) is used and this does not set _uid so it is nil.
To inject _uid into your templates, simply say loading_panels._uid = _uid in your two if let loading_panels = ... blocks.
You might also want to read section "Follow case conventions" in the Swift API Design Guidelines to brush up on your naming.
I've got a UIControl class and need to do some calculation based on UIImageView location which can be moved with touchesBegan and touchesMoved (everything inside this class).
Than I would like to display it as a UILabel which I've created programmatically.
class control : UIControl{
...
let leftControl: UIImageView = UIImageView(image: UIImage(named: "left-control"))
...
func leftValue() -> String{
var leftValue : String = "0.0"
leftValue = "\(leftControl.center.x)"
return leftValue
}
}
and my ViewController.swift
class ViewController: UIViewController {
let ctrl : Control = Control()
let leftLabel : UILabel = UILabel(frame: CGRect(x: 40, y: 300, width: 150, height: 30))
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
ctrl.frame.origin = CGPoint(x: 40, y: 400)
leftLabel.text = "\(ctrl.leftValue())" //displays only starting value
view.addSubview(slider)
view.addSubview(leftLabel)
view.addSubview(rightLabel)
}
I know that it's inside the viewDidLoad so it's not updating properly. I was wondering about scheduledTimer but don't know if it's good solution.
You can achieve this using protocols and delegation - in the file for your Control add this :
protocol ControlDelegate: class {
func controlPositionDidChange(leftValue: String)
}
And add a weak var delegate: ControlDelegate? inside Control class.
In the file for view controller make following changes :
class ViewController: UIViewController, ControllDelegate {
let ctrl : Control = Control()
let leftLabel : UILabel = UILabel(frame: CGRect(x: 40, y: 300, width: 150, height: 30))
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
ctrl.frame.origin = CGPoint(x: 40, y: 400)
ctrl.delegate = self
leftLabel.text = "\(ctrl.leftValue())" //displays only starting value
view.addSubview(slider)
view.addSubview(leftLabel)
view.addSubview(rightLabel)
}
func controlPositionDidChange(leftValue: String) {
leftLabel.text = leftValue
}
}
Now, whenever you want to inform the delegate that your control has changed the position, simply call self.delegate?.controlPositionDidChange(self.leftValue()) in appropriate places.
As usually, there is more in the docs. I highly suggest reading through them as delegation and protocols are widely used in CocoaTouch.
The answer of #Losiowaty describes the solution that most developers choose. But there are (in my opinion) much better ways to achieve it. I prefer the object oriented solution. It might look like more code, but its a much better maintainable way with more reusable code.
A real object oriented solution of your problem might look like that:
// reusable protocol set
protocol OOString: class {
var value: String { get set }
}
// reusable functional objects
final class RuntimeString: OOString {
init(initialValue: String = "") {
self.value = initialValue
}
var value: String
}
final class ViewUpdatingString: OOString {
init(_ decorated: OOString, view: UIView) {
self.decorated = decorated
self.view = view
}
var value: String {
get {
return decorated.value
}
set(newValue) {
decorated.value = newValue
view.setNeedsLayout()
}
}
private let decorated: OOString
private let view: UIView
}
// reusable ui objects
final class MyLabel : UILabel {
init(frame: CGRect, imageXCenter: OOString) {
self.imageXCenter = imageXCenter
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("Not supported")
}
override func layoutSubviews() {
super.layoutSubviews()
text = imageXCenter.value
}
private let imageXCenter: OOString
}
final class MyControl : UIControl {
init(frame: CGRect, imageXCenter: OOString) {
self.imageXCenter = imageXCenter
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("Not supported")
}
private let imageXCenter: OOString
private let leftControl = UIImageView(image: UIImage(named: "left-control"))
// call this at change
private func updateValue() {
imageXCenter.value = "\(leftControl.center.x)"
}
}
// non reusable business logic
final class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let dependency = RuntimeString(initialValue: "unset")
let leftLabel = MyLabel(
frame: CGRect(x: 40, y: 300, width: 150, height: 30),
imageXCenter: dependency
)
let control = MyControl(
frame: CGRect(x: 40, y: 400, width: 400, height: 400),
imageXCenter: ViewUpdatingString(dependency, view: leftLabel)
)
view.addSubview(leftLabel)
view.addSubview(control)
}
}
The main idea is to extract the dependency of both objects into another object and use a decorator then to automatically update the ui on every set value.
Note:
this approach follows the rules of object oriented coding (clean coding, elegant objects, decorator pattern, ...)
reusable classes are very simple constructed and fullfill exactly one task
classes communicate by protocols with each other
dependencies are given by dependency injection as far as possible
internal object functionality is private (loose coupling)
everything (except the business logic) is designed for reuse -> if you code like that the portfolio of reusable code grows with every day you code
the business logic of your app is more concentrated in one place (in my real coding its even outside the UIViewController)
unittesting of reusable objects is very simple when using fake implementations for the protocols (even mocking is not needed in most cases)
lesser problems with retain cycles, in most cases you do not need weak properties any more
avoiding Null, nil and Optionals (they pollute your code)
...
So I have created a few of classes which are subclasses of UIView. Let's say I have a class called "ExampleClass". I thought that I could use that class by creating a UIView and setting its class to "ExampleClass" under the Identity-tab. However, whenever I do this I'll get an error saying;
fatal error: init(coder:) has not been implemented: file /Users/[My Name]/Desktop/xcode/[Project Name]/ExampleClass/ViewController.swift, line [n]
When I create a similar View programmatically by saying:
let MyView = ExampleClass
MyView.frame = CGRect(x: 0, y: 0, width: 100, height: 400)
I do not get this error.
The class does contain the required init, which is the cause of this error:
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
You have to call super.init(coder: aDecoder) inside yourinit` function
I'm trying to place a view from a custom view class on my main view controller programatically. Here is my code:
import UIKit
class BarControl: UIView {
// MARK: Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.redColor()
button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown)
addSubview(button)
}
override func intrinsicContentSize() -> CGSize {
return CGSize(width: 240, height: 44)
}
}
And in my main view controller:
let myView = BarControl()
An error comes up saying "Missing argument for parameter 'coder' in call"
What should I put in the brackets of let myView = BarControl()?
When I subclass UIView I always override init(frame: CGRect) and add any additional implementation. Then xcode usually tells you that - ‘required’: initializer ‘init(coder:)’ must be provided by subclass of ‘UIView’ and if you click that error and hit enter it automatically added that extra initializer. Well, I've been doing this without fully understanding why I have to add that extra initializer I never use. And your question finally made me look for the reason. I found a good tutorial about this initializer confusion(?) in the following link.
http://www.edwardhuynh.com/blog/2015/02/16/swift-initializer-confusion/
It's a short article about your confusion(?) =)
Basically the answer for your question is you should override init () to call BarControl() instead of init(coder aDecoder: NSCoder!). In addition, you have to add all the UIView's initializers to use the initializer you want to use according to the article.
The fonction prototype is looking like this:
required init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
// Your code here
}
I dont think you need the optional?