I am trying to make a custom UIButton subclass that has different colors in the normal, selected, and disabled states. My button lives in a framework that is then imported into an app, but every code snippet I place here, I have tried in both the main app and the framework--I know it shouldn't make any difference, but I wanted to cover my bases. I can't get it to work to save my life.
class BrokenButton: UIButton {
override var isEnabled: Bool {
didSet {
print("This is never called no matter what I do")
}
}
}
I've tried using KVO to watch the value of isEnabled since overriding the setter did not work:
class BrokenButton2: UIButton {
required init() {
super.init(frame: .zero)
addObserver(self, forKeyPath: #keyPath(isEnabled), options: [.new], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
print("Never called")
}
}
I'm at my wit's end here. What am I getting wrong about this?
#Daniel As the BrokenButton class is inside the Framework, you need to use open keyword for accessing from outside the other modules. So just add the open keyword before the BrokenButton class and isEnabled property.
open class BrokenButton: UIButton {
override open var isEnabled: Bool {
didSet {
print("This is never called no matter what I do")
}
}
}
An open class is accessible and subclassable outside of the defining
module. An open class member is accessible and overridable outside of
the defining module.
for further info regarding open keyword..read this stackoverflow answer
I figured the same as meaning-matters. Here are some steps you can take to reproduce a way in which it works. It's possible you missed any one of these steps.
Create BrokenButton class which is a subclass of UIButton. Just as you did in your question above.
Open up storyboard or xib and drag a UIButton into your storyboard or xib
Select the UIButton you just dragged into the storyboard/xib, and in the identify inspector, make sure you make the class BrokenButton
Create an Outlet in your ViewController something like this:
#IBOutlet weak var button: BrokenButton?
In your storyboard/xib, connections inspector, connect the button to your IBOutlet
Then in your viewcontroller, set the button to be enabled or disabled like this:
button?.isEnabled = true
Here it is in the works.
Related
I needed to delegate a click action for my UIView class to my UIViewController class since Swift does not support multiple class inheritance. So i wanted it such that once a button is clicked on my subview, a function in my BrowserViewController class is called.
I am using a protocol to achieve this, but on the function does not triggered when the button is tapped. Please help me out.
View Controller
class BrowseViewController: UIViewController {
var categoryItem: CategoryItem! = CategoryItem() //Category Item
private func setupExplore() {
//assign delegate of category item to controller
self.categoryItem.delegate = self
}
}
// delegate function to be called
extension BrowseViewController: ExploreDelegate {
func categoryClicked(category: ProductCategory) {
print("clicked")
let categoryView = ProductByCategoryView()
categoryView.category = category
categoryView.modalPresentationStyle = .overCurrentContext
self.navigationController?.pushViewController(categoryView, animated: true)
}
}
Explore.swift (subview)
import UIKit
protocol ExploreDelegate: UIViewController {
func categoryClicked(category: ProductCategory)
}
class Explore: UIView {
var delegate: ExploreDelegate?
class CategoryItem: UIView {
var delegate: ExploreDelegate?
var category: ProductCategory? {
didSet {
self.configure()
}
}
var tapped: ((_ category: ProductCategory?) -> Void)?
func configure() {
self.layer.cornerRadius = 6
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.categoryTapped)))
self.layoutIfNeeded()
}
#objc func categoryTapped(_ sender: UIGestureRecognizer) {
delegate?.categoryClicked(category: ProductCategory.everything)
self.tapped?(self.category)
}
}
}
Simply add a print statement inside categoryTapped.
You will then know if it is actually being tapped.
A million things could go wrong, for example, you may have forget to set the UIView to allow intertaction.
After checking that. Next add another print statement inside categoryTapped which shows you whether or not the delegate variable is null.
You'll quickly discover the problem using simple print statements.
print("I got to here!")
It's that easy.
And what about
if delegate == nil { print("it is nil!! oh no!" }
else { print("phew. it is NOT nil.") }
Debugging is really that easy at this level.
Next add a print statement inside setupExplore()
func setupExplore() {
print("setup explore was called")
....
See what happens.
I don't see any piece of code which sets the delegate.
First of all, define delegate as a property inside CategoryItem class, Then you must set the current instance of BrowseViewController to the delegate variable of CategoryItem. Now you can expect your method being called.
There are a few things that could cause the delegate method to not be triggered in this code:
Ensure that isUserInteractionEnabled = true on your CategoryItem. This is probably best done in either the configure() function in the CategoryItem or in the setupExplore() function of the BrowseViewController.
Make sure that the setupExplore() function on the BrowseViewController is being called, and that the category is being set on the CategoryItem to trigger the configure function. Otherwise, either the delegate or the gesture recognizer might not being set.
Side Note - weak vs strong delegate
On a side note, it is usually best practice to make your delegate properties weak var rather that having them be a strong reference, as this makes them prone to strong retain cycles.
Therefore, you might want to consider making the var delegate: ExploreDelegate? on your CategoryItem into weak var delegate: ExploreDelegate?. For more information on this problem, view this post.
I wrote a custom class called PressableView. It recognizes taps and then calls a protocol function on its delegate object. Since it is a subclass of UIView, it does not allow the connection of IBActions by default.
I was wondering whether it is possible to connect a function inside the view controller, the pressable view is in, to the object, so it calls that method on tap – just like you would with a UIButton.
I already tried things like:
class PressableView: UIView {
// ...
#IBOutlet var action: (() -> Void)?
// ...
}
...but, Xcode doesn't allow that type to be an IBOutlet.
Any ideas?
change PressableView parent class as UIControl class then you can connect for actions and handle it.
UIControl is subclass of UIView class only. so you will have all the properties of UIView as well.
class PressableView: UIControl {
#IBAction func uicontrolEventAction(_ sender: Any) {
}
}
The TextView delegate is set:
textView.delegate = self //self being a UITextViewDelegate
but the delegate method doesn't get called when the text is set programmatically
func textViewDidChange(_ textView: UITextView) {
print(textView.text)
}
How to respond to text changes without going reactive?
This is how UITextField (and most of UIKit controls) behave- doesn't trigger event when set programatically. It makes sense- lets you avoid recurring, infinite calls.
If you really want to be notify when text is changed programatically, you have to subclass UITextField and override text property (probably attributedText also). Then in didSet block call delegate method.
Don't forget that UITextField inherits from UIControl- I would also call sendActions(for:) to make target-action mechanism fire.
I just tried KVO on UITextView,
self.textView1.text = "You are working, but I will change you in 5 seconds"
Add your observer
self.textView1.addObserver(self, forKeyPath: "text", options: NSKeyValueObservingOptions(rawValue: 0), context: nil)
Trigger text change programmatically, just an example do it the way you want.
DispatchQueue.main.asyncAfter(deadline:.now() + 5) {
self.textView1.text = "Changed after some time"
}
Override the KVO method.
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object == self.textView1{
//do your thing here...
}
}
FYI from Apple docs below
Note: Although the classes of the UIKit framework generally do not
support KVO, you can still implement it in the custom objects of your
application, including custom views.
https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/KVO.html
declare your text view variable with #objc dynamic
declare and hold a variable with type NSKeyValueObservation
use function observe(_:changeHandler:) bind your text view's text property, hold the return value with variable declared in step 2
observe changes in changeHandler
example:
#objc dynamic private var textView: UITextView!
private var observation: NSKeyValueObservation?
func bind() {
observation = observe(\.textView.text, options: [.old, .new]) { object, change in
print(object, change)
}
}
You have misspelled the delegate with textView with _textView
func textViewDidChange(textView: UITextView) { //Handle the text changes here
print(textView.text); //the textView parameter is the textView where text was changed
}
Put this in viewDidLoad
textView!.delegate = self
For simplicity, let's say I want to create a custom UITextField and I want to add a simple behaviour to it; Which is, if the textfield becomes the first responder, the background color would be changed to green.
To do so, in my custom class I have to set the class as the delegate to receive the event of becoming first responder. But the thing is that if the user of this custom textfield set itself as the delegate the events are not sent to the custom textfield(Since only one object can be the delegate of another object)
I can manually forward all the events, but I'm looking for a cleaner and more scalable solution.
Here's a sketch of the situation:
class MyTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
}
extension MyTextField: UITextFieldDelegate {
func textFieldDidBeginEditing(textField: UITextField) {
backgroundColor = UIColor.greenColor()
}
}
but if the user of MyTextField do this:
class ViewController: UIViewController {
#IBOutlet weak var myTextField: MyTextField!
override func viewDidLoad() {
super.viewDidLoad()
myTextField.delegate = self
}
}
the behaviour won't work; because the delegation relationship to MyTextField is gone.
NOTE: I'm not only interested in becoming first responder problem, rather it's about using any methods of the delegate, with capability of the user of my custom UITextField setting itself as the delegate, at the same time.
Thanks, in advance.
As you say, most delegation is restricted to a single object as the delegate.
Since a text field is a responder, you should be able to override func becomeFirstResponder() -> Bool to change the color, while letting the user of the object handle the delegation as it expects.
UIResponder docs: "Subclasses can override this method to update state or perform some action such as highlighting the selection."
I'm fairly new to iOS development and I ran into a problem which seems simple yet I cannot solve it whichever way I try.
I have a custom view class #IBDesignable class ValidatedInputFieldView: UIView that hold 2 UI elements.
One of those is UITextField.
ValidatedInputFieldView is added to its parent view class ViewController: UIViewController, UITextFieldDelegate.
I want ViewController to respond to textFieldShouldReturn event from UITextField, and not the ValidatedInputFieldView where the UITextField is in.
I've tried exposing the delegate field of the UITextView:
#IBOutlet var textFieldDelegate:UITextFieldDelegate?
And setting it in the ValidatedInputFieldView:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
contentTextField.delegate = textFieldDelegate
}
And then linking it in the IB using the workaround:
Declare the outlet's type as AnyObject or NSObject, connect objects to the outlet using Interface Builder, then change the outlet's type back to the protocol.
But it simply does not work.
Debug says the object is nil.
I'm having trouble understanding how are those events that happen in Subview passed to the Parent view and what should I use to expose delegates.
A typical style I like to follow is to keep the items contained in their Views. What I would do is put the textFieldShouldReturn function in the ValidateInputView and set the delegate of the UITextField to the ValidateInputView. Then, in the textFieldShouldReturn function, post a notification using
NSNotificationCenter.defaultCenter().postNotificationName("textFieldFunction", object: self)
and make sure you are listening in the ViewController for this with:
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: Selector("textFieldFunction:"),
name: "textFieldFunction",
object: nil)
and create the function textFieldFunction in the ViewController to handle it like this:
func textFieldFunction(notification: NSNotification) {
//Put code to handle return press here
}
Make sure you put this in the ViewController as well:
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
OR for no notification
Use the solution in this sample project:
https://mega.nz/#!koYDTZpa!uGZ6oUbKxRaWuGSM9FbCuV0t8oz5mtu35rZlLEL7Ehs
Sorry, I would post code, but there isn't much since it is mostly all through the IB.