Changing a delegate's property through protocol - ios

I have controllingView, that needs to change a property in presentingView. They are both in the same ViewController.
I can let them communicate by making presentingView delegate of controllingView. But it would be far more elegant and flexible, if I could just change the property directly (since
I need to change the presentingView's property's property actually)
I have seen it done in this question: Accessing protocol property in Swift class.
But in controllingView, calling delegate.propertyINeedToChange is nil.
How do I change a delegate's property from the delegating object?
Here is the code:
class MainViewController : UIViewController {
var controllingView = ControllingView()
let presentingView = PresentingView()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
view.addSubview(controllingView)
view.addSubview(presentingView)
self.view = view
controllingView.delegate = presentingView
}
}
class ControllingView: UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
//ControlsView Setup
self.backgroundColor = UIColor(displayP3Red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0)
setupViews()
}
let testSLDR = UISlider()
var delegate: ControlsViewDelegate?
func setupViews() {
self.addSubview(testSLDR)
testSLDR.addTarget(self, action: #selector(testSLDRchanged), for: .valueChanged)
}
#objc func testSLDRchanged() {
delegate?.button?.backgroundColor = UIColor.red
}
}
class PresentingView: UIView, ControlsViewDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
let button = Button()
self.addSubview(button)
}
var button: Button?
}
protocol ControlsViewDelegate {
var button: Button? { get set }
}
class Button: UIButton { ... }

As the views are initialized in the same view controller you don't need protocol / delegate
Delete the protocol and the associated code
In ControllingView declare a weak Button property
weak var presentingButton : Button?
Replace the line to set the delegate with a line to assign the button of PresentingView to the presentingButton property
controllingView.delegate = presentingView
controllingView.presentingButton = presentingView.button
In the action change the color
#objc func testSLDRchanged() {
presentingButton?.backgroundColor = UIColor.red
}

Related

Init a property in a class with an override init and a required init? [duplicate]

I tried to add a double value to a subclass of UIButton in Swift. I tried all kind of inits and get and set options, but I couldn’t get it to work.
So I started with this:
class CVSTButton : UIButton {
var cvstPosition: Double
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
}
}
Then I tried:
class CVSTButton : UIButton {
var cvstPosition: Double {
get {
return self.cvstPosition
}
set {
self.cvstPosition = newValue
}
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
}
}
What’s is wrong here?
With Swift 3, according to your needs, you may choose one of the seven following code snippets to solve your problem.
1. Create your UIButton subclass with a custom initializer
This solution allows you to create instances of your UIButton subclass with the appropriate value for your property. With this solution, you can only create instances of your UIButton subclass programmatically.
import UIKit
class CustomButton: UIButton {
var myValue: Int
required init(value: Int = 0) {
// set myValue before super.init is called
self.myValue = value
super.init(frame: .zero)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = CustomButton(value: 0)
// let button = CustomButton() // also works
button.setTitle("Hello", for: .normal)
// auto layout
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
print(button.myValue) // prints 0
}
}
2. Create your UIButton subclass with a convenience initializer
This solution allows you to create instances of your UIButton subclass with the appropriate value for your property. With this solution, you can only create instances of your UIButton subclass programmatically.
import UIKit
class CustomButton: UIButton {
var myValue: Int
convenience init(squareOf value: Int) {
self.init(value: value * value)
}
required init(value: Int = 0) {
// set myValue before super.init is called
self.myValue = value
super.init(frame: .zero)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = CustomButton(squareOf: 10)
// let button = CustomButton(value: 100) // also works
button.setTitle("Hello", for: .normal)
// auto layout
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
print(button.myValue) // prints 100
}
}
3. Create your UIButton subclass with init(frame: CGRect) initializer
With this solution, you can only create instances of your UIButton subclass programmatically.
import UIKit
class CustomButton: UIButton {
var myValue: Int
override init(frame: CGRect) {
// set myValue before super.init is called
self.myValue = 0
super.init(frame: frame)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = CustomButton(frame: .zero)
//let button = CustomButton() // also works
button.setTitle("Hello", for: .normal)
// auto layout
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
print(button.myValue) // prints 0
}
}
4. Create your UIButton subclass with init?(coder aDecoder: NSCoder) initializer
With this solution, you can create instances of your UIButton subclass from Storyboard.
import UIKit
class CustomButton: UIButton {
var myValue: Int
required init?(coder aDecoder: NSCoder) {
// set myValue before super.init is called
self.myValue = 0
super.init(coder: aDecoder)
// set other operations after super.init, if required
backgroundColor = .red
}
}
Usage:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button: CustomButton!
override func viewDidLoad() {
super.viewDidLoad()
print(button.myValue) // prints 0
}
}
5. Create your UIButton subclass with init(frame: CGRect) and init?(coder aDecoder: NSCoder) initializers
With this solution, you can create instances of your UIButton subclass programmatically or from Storyboard.
import UIKit
class CustomButton: UIButton {
var myValue: Int
override init(frame: CGRect) {
// set myValue before super.init is called
self.myValue = 0
super.init(frame: frame)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
// set myValue before super.init is called
self.myValue = 0
super.init(coder: aDecoder)
// set other operations after super.init if required
backgroundColor = .red
}
}
6. Create your UIButton subclass with a default property value for your property
As an alternative to the previous solutions, you can assign an initial value to your property outside of the initializers.
import UIKit
class CustomButton: UIButton {
var myValue: Int = 0
override init(frame: CGRect) {
super.init(frame: frame)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// set other operations after super.init if required
backgroundColor = .red
}
}
7. Create your UIButton subclass with your property having an optional type
If you don't want to / can't set a default value to your property when your button is created, you must set your property type as an optional.
import UIKit
class CustomButton: UIButton {
var myValue: Int? = nil
override init(frame: CGRect) {
super.init(frame: frame)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// set other operations after super.init if required
backgroundColor = .red
}
}
You need two things there -- (1) cvstPosition needs an initial value, either in the declaration or in the init before you call super.init(). (2) That call to fatalError is put in so you don't forget to implement the initializer -- it’s basically an on-purpose crash. Delete!
Setting the initial value in the declaration, there isn’t any need for an init:
class CVSTButton : UIButton {
var cvstPosition: Double = 0
}
Or setting the initial value in the initializer:
class CVSTButton : UIButton {
var cvstPosition: Double
required init(coder aDecoder: NSCoder) {
cvstPosition = 0
super.init(coder: aDecoder)
}
}
Swift >= 2.2:
Since this version subclassing the UIButton, makes your button to have .custom type.
Swift 2:
convenience init(type buttonType: UIButtonType) {
super.init(frame: CGRectZero)
// this button be automatically .Custom
}
Swift:
override class func buttonWithType(buttonType: UIButtonType) -> AnyObject {
let button = super.buttonWithType(buttonType) as! UIButton
// your default code
return button
}
Note: I'm using Swift 3 in Xcode 8.3.3
This is a simple and easy workaround I've been using when needing to add custom properties and methods to a UIButton:
class CVSTButton: UIButton {
var cvstPosition: Double
static func button(withCVSTPosition cvstPosition: Double) -> CVSTButton {
let button = CVSTButton(type: .detailDisclosure) // You may adjust the initializer used to suit your needs.
button.cvstPosition = cvstPosition // Then you can simply set the the properties (which are passed as arguments to the factor/class method)
return button
}
}
To use it:
let cvstButton = CVSTButton.button(withCVSTPosition: 2.0)

How to change UITextFields default border color

How can I set a default border color for UITextFields which are not in focus when the view appears? Tried this with no success:
class UITextFieldCustom : UITextField, UITextFieldDelegate {
init(frame: CGRect, size: CGFloat) {
super.init(frame: frame)
self.layer.borderColor = UIColor.gray.cgColor
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
If you can't see new border, try to increase its width:
layer.borderWidth = 2.0
The problem you are facing is that self.layer.borderColor = UIColor.gray.cgColor is not called, because the function init(frame: CGRect, size: CGFloat) is not called. The reason why it is not called is because your textfield is not initilized in your code, for example:
func createTextField() -> UITextfield {
return UITextfield(frame: .zero, size: 100.0)
}
Your textfield is initilized from the storyboard. That is why the other init function required init?(coder aDecoder: NSCoder) exists. The sotryboard calls this function to initalize the textfield.
To solve the problem you just need to add your border modification line into the other init function, like this:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.borderColor = UIColor.gray.cgColor
}
Although one other clean approach I prefere more, is to add all setup code into the viewDidLoad method, for a clean overview, like this:
class ViewController: UIViewController {
// MARK: - Properties
#IBOutlet weak var textfield: UITextfield!
// MARK: - View's Lifecycle
override func viewDidLoad {
super.viewDidLoad()
setupTextfield()
}
// MARK: - Setup
private func setupTextfield() {
textfield.layer.borderColor = UIColor.gray.cgColor
}
}
just place your code here as well, you probably don't call this from storyboard or nib.
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.borderColor = UIColor.gray.cgColor
}
use #IBDesignable class to create the border color and set the width, like this example.
import UIKit
#IBDesignable
class BorderTextField: UITextField {
#IBInspectable var borderColor: UIColor = UIColor.white{
didSet{
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet{
layer.borderWidth = borderWidth
}
}
}
the you can set the value in storyboard as needed.

UITextView does not change textColor property

I have a UITextView custom class:
class TitleTextView: UITextView {
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
func setup() {
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
textColor = .brand100
backgroundColor = .clear
isUserInteractionEnabled = false
textAlignment = .left
isScrollEnabled = false
let frameWidth = Constants.screenSize.width * 87.5 / 100
font = UIFont.OpenSans(.semibold, size: (frameWidth * 8.55 / 100))
}
}
I used this text view custom class inner a UIView.
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
And I called this UIView in a UIViewController.
private func setupTitleView() {
let titleView = UINib(nibName: "TitleView", bundle: .main).instantiate(withOwner: nil, options: nil).first as! UIView
titleView.frame = contentHeaderView.bounds
contentHeaderView.addSubview(titleView)
view.layoutIfNeeded()
}
But when I set the textColor property in my custom UIView (MyCustomHeaderView) the color doesn't change.
Do you have any idea about why the reason that my UITextView doesn't apply the color that I set in my custom UIView?
 
I called layoutIfNeed() but this doesn't work.
It's because you are doing everything inside the layoutSubviews Which in itself is really bad practice.
In your case you instantiate the CustomHeaderView and the layout for that is called, hence calling layoutSubviews next step is that the textView is added to your CustomHeaderView and then the textView's layoutSubviews is called and will override your color.
You can solve this in two ways i believe. Altho i don't work with Nibs and storyboards,
first:
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
Second, this is a big maybe:
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
defer {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
}
Defer will wait till everything is been initialised before running whatever is in the block. I don't know tho how that works with layoutSubviews

UISwitch inside customFooterView needs to call a method outside of class its declared in using Swift

I created a customTableViewFooter programmatically like this:
import UIKit
class CustomFooterView: UIView {
let myLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14, weight: UIFontWeightMedium)
label.textColor = UIColor.black
label.numberOfLines = 1
label.text = "Blah blah blah"
label.textAlignment = .center
return label
}()
let mySwitch: UISwitch = {
let mySwitch = UISwitch()
mySwitch.addTarget(self, action: #selector(switchChanged), for: .valueChanged)
return mySwitch
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
self.layout()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
self.setup()
self.layout()
}
private func setup() {
self.addSubview(myLabel)
self.addSubview(mySwitch)
}
private func layout() {
anonymousLabel.constrainCenterVertically()
anonymousLabel.constrainCenterHorizontally()
anonSwitch.constrainCenterVertically()
anonSwitch.constrainTrailing(at: 20)
}
func switchChanged() {
print("hello")
}
}
Inside my class which implements the UITableView, I add this footer programmatically:
class MyViewController: UIViewController {
var footerView = CustomFooterView()
override func viewDidLoad() {
super.viewDidLoad()
addFooter()
}
func addFooter() {
tableView.tableFooterView = footerView
}
...
}
At the moment, I have the method: switchChanged inside the class where I declare my UISwitch, and what I would like to do, is move this method to my ViewController, so the that the UISwitch, when selected by the user, calls a method inside my ViewController. How would I do this?
This should work (as already suggested in a comment)
footerView.mySwitch.addTarget(self, action: #selector(aSelector), for: .valueChanged)

iOS - UIButton become first responder to open keyboard

I need to open keyboard on button click for UIButton (not using/for UITextField). I have tried to create custom button by overriding variable canBecomeFirstResponder but it's not working.
Is there any other way to do so?
Note: I want to set UIPIckerView as an input view of UIButton in key board frame.
Here is my code.
class RespondingButton: UIButton {
override var canBecomeFirstResponder: Bool {
return true
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
// common init
}
}
In my view controller, I connected button action.
class TestViewController: UIViewController {
#IBAction func testBecomeFirstResponder(button: RespondingButton){
button.becomeFirstResponder() // Not working.
}
}
Here is what I would do.
Create transparent textField 1x1px, lets say it is myTextField.
Then add your desired button. In the button action make the myTextField.becomeFirstResponder().
Create view:
let pvBackground: UIView = {
let v = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
v.backgroundColor = .white
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
In viewDidLoad:
pvBackground.addSubview(yourPickerView)//add the picker into the pvBackground
myTextField.inputView = pvBackground
I added the pickerView into the another view to be able to customize it more.
Add conformance to UIKeyInput like this. It should work.
class RespondingButton: UIButton, UIKeyInput {
override var canBecomeFirstResponder: Bool {
return true
}
var hasText: Bool = true
func insertText(_ text: String) {}
func deleteBackward() {}
}

Resources