Override UI element .enabled property in Swift - ios

I want to override property of UIView class to do extra lines of code each time property value of .enabled is changed. How do I do that?
For example:
There is MyUIView class myClass
There is myClass.enabled property
I want to override this to add next things
Edit subview of type UIView and set background to red colour if enabled/green if disabled.
Edit subview of type UIButton and make it disabled if disabled/enabled if enabled.

The code will look something like this -
class SubClass: UIView {
override var userInteractionEnabled: Bool {
didSet {
// Do the color change and other stuff.
// Use oldValue to access old value
}
}
}
There is much more to explore about this setter and getter.

Related

Round button on custom class with UIAppearance

I am trying to apply styles on a custom class using UIAppearance()
class MainStyleButton: UIButton {}
with a code:
let buttonView = MainStyleButton.appearance()
buttonView.backgroundColor = Style.buttonColor
buttonView.layer.cornerRadius = 5
buttonView.layer.borderWidth = 5
buttonView.layer.borderColor = Style.buttonColor.cgColor
It works with color, but unfortunately doesn't make my button round. I would appreciate any tips.
Tested on simulator iPhone X, 8 with iOS 11.2.
I tried replicating your approach and set up a button. I tried to change the button's appearance in a UIViewController during viewDidLoad and also in the AppDelegate during applicationDidFinishLaunching using your code. I additionally tested changing the button type to .custom from the default type .system. None of this seemed to work, I could not override the same attributes that you couldn't.
From Apple's docs I understand that the button type defines its appearance and also which appearance attributes can be overridden:
A button’s type defines its basic appearance and behavior. You specify the type of a button at creation time using the init(type:) method or in your storyboard file. After creating a button, you cannot change its type.
I do not know why the attributes of interest to you are not changeable at this point
However I would like to offer a different approach that I personally use and allows you to change the buttons appearance. Since you already defined your custom class it is much simpler to define corner radius and other attributes that you would like, like so (or you could write a style function with parameters that you can call at any time, to be able to change the appearance based on where the button is used):
class MainStyleButton: UIButton {
override func awakeFromNib() {
layer.borderColor = Style.buttonColor.cgColor
layer.borderWidth = 5
layer.cornerRadius = 5
}
}
Or you can instantiate/use an IBOutlet for a system button and do this:
class MyViewController: UIViewController {
#IBOutlet weak var myButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// not necessary to do this is viewDidLoad, that's just my example
myButton.layer.borderColor = Style.buttonColor.cgColor
myButton.layer.cornerRadius = 5
myButton.layer.borderWidth = 5
}

Lazy UILabel property of custom control

I want to be able to set the title of my custom control in interface builder and implement the title as a UILabel. I have created my properties thus:
#IBInspectable var titleText: String? {
get {
return title.text
}
set(newTitleText) {
title.text = newTitleText
}
}
#IBInspectable lazy var title: UILabel = UILabel.init()
Is this a good/recommended way to do this? I also want the control to layout the UILabel if the title text has been set and plan to do this by overriding layoutSubviews - again is this recommended or would you suggest a different pattern?
My ultimate aim is to be able to enable a designer to configure my control from IB and exposing the UILabel as a property that can be configured in IB would be my ideal.
#IBInspectable can be used only with the following types:
Int
CGFloat
Double
String
Bool
CGPoint
CGSize
CGRect
UIColor
UIImage
So, no, you can not "expose a UILabel" for design-time manipulation.
First, I don't think that you need to make your variable lazy here, also, if you want to create a new instance of UILabel, it's better to user the constructor UILabel().
Now, I am not sure if I understand correctly your question, but if I did, then you can expose your custom view properties (a string that represents your title label for example) to the Attributes Inspector.
You would do it like so:
#IBDesignable
class CustomView: UIView {
#IBOutlet weak var titleLabel: UILabel!
#IBInspectable var title: String? {
didSet { self.titleLabel.text = title }
}
}
To break up the code above :
First you make the class (your custom view) #IBDesignable so that it supports live preview in the Interface Builder.
Then, inside your custom view class, you create a title property (which is a String) and make sure to make it #IBInspectable so that you can change this property later via the Attributes Inspector.
Finally you add a property observer ( didSet ) so that the text property of your custom label is updated whenever you change the title property from the Attributes Inspector.

How to override UIButton's init with button type in Swift?

I want to subclass UIButton and add to it a property isActive, which is a Bool that changes the look of the button (this is not related to the default .enabled property).
However, I also want to set the button type when initialized. Specifically, I want to initialize it much like it is in UIButton, like:
let button = MyButton(type: .Custom)
However, because the .buttonType is a read-only, I cannot just override it like:
convenience init(type buttonType: UIButtonType) {
buttonType = buttonType
self.init()
}
This spits out an error: Cannot assign to value: 'buttonType' is a 'let' constant (I already implemented the required init(coder:)).
So how can I set the .buttonType when initialized?
NOTE: The custom property shall be also used in other functions to change the logic within it, so I picked up a property, instead of defining two functions to change the look.
You don't need use it, according to documentation:
This method is a convenience constructor for creating button objects with specific configurations. If you subclass UIButton, this method does not return an instance of your subclass. If you want to create an instance of a specific subclass, you must alloc/init the button directly.
When creating a custom button—that is a button with the type UIButton.ButtonType.custom—the frame of the button is set to (0, 0, 0, 0) initially. Before adding the button to your interface, you should update the frame to a more appropriate value.
means button you created already custom, just use init()
buttonType is read-only, so you have to set the type via UIButton's convenience initializer:
convenience init(type buttonType: UIButtonType) {
self.init(type: buttonType)
// assign your custom property
}

Lazily Override IBOutlet Object's Properties

I have a custom UItextView subclass where I override canBecomeFirstResponder():
class MyTextView: UITextView {
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
override func canBecomeFirstResponder() -> Bool {
return false
}
}
I'm doing this to allow the data detected links (phone numbers are URLs) in a UITextView to function without any of the other text in the view to be selectable, but that is unrelated to the question.
canBecomeFirstResponder() is the only property/method I want to override, so subclassing seems like overkill. I use this custom class with a view created with Interface Builder. Is there a way I can lazily override an IBOutlet's of a UIKit object's properties? Something like this:
#IBOutlet weak var contactTextView: UITextView! {
override func canBecomeFirstResponder: Bool = {
return false
}
}
I do not want to use an extension on UITextView because I only want to override canBecomeFirstResponder() for a specific UITextView, not every one used in my project.
If you want to override, you have to subclass. These two concepts are connected. You cannot override without subclassing.
Your "like this" idea wouldn't work in any language. Basically, you are describing an anonymous subclass which is available in Java, for instance (not in Swift). However, that would have to be used during class instantiation, not when declaring a variable.
Of course, you could swizzle canBecomeFirstResponder with a method returning false in didSet but that's much more complicated than subclassing.
The answer is that with the current versions of swift, there is no way to do that.
Based on what you are describing, it might be possible that you could make your view controller implement UITextViewDelegate and implement textViewShouldBeginEditing to return false.
This is just a guess based on the fact that you're seemingly trying to override canBecomeFirstResponder to disallow typing.
That all being said, as was pointed out if you return false from canBecomeFirstResponder I think the expected behavior is that the UI element will no longer allow itself to capture user input. From your responses in the comments, it seems that its capturing user input for you anyway, but that might just be a the particular version of iOS you're running. It could also be that my understanding of the first responder chain is incorrect.

Swift - Access IBOutlet in other class

I have a UIView with a TableView and a Button (Big Button). The TableView has a custom Cell. In this cell there is an "Add" button. I want to animate the first button when the user makes click on the Add button.
This is my schema:
This is my code:
class ProductsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
#IBOutlet var bigButton: UIButton! <- I WANT TO ANIMATE THAT BUTTON
}
ProductCell class
class ProductCell: UITableViewCell {
#IBAction func addProduct(sender: AnyObject) {
//I WANT TO ACCESS THE BIG BUTTON FROM HERE
}
}
Screen example of my app
I've tried to get the parent controller or the superview to get the IBOutlet but the app is crashing allways
Add block properties to your cells which lets them notify your view controller when they have been clicked. In your view controller block code, you can then access the big button.
See my answer to a similar question. Simply replace the switch example with your button. So replace UISwitch with UIButton.
How can I get index path of cell on switch change event in section based table view
So rather than have the cell try and talk to another cell/button, have the cell notify the controller which can then manage the big button changes.
Although I made a comment about using alternate methods you could also employ a strategy below based on updates to a property stored in the current view controller class. You could just as well use property observation on the ProductsViewController but I assume you'd like to keep OOP focused and reduce the size of your controller.
Subclass the ViewController
One could subclass an existing UIViewController and then create a property in the super class that deals with the value that was changed (row tapped). In that subclass you could then do some animation. Because you would be subclassing you continue to obtain all the benefits and methods defined in your existing controller. In your identity inspector point your Class to the new subclass and create any functional updates to your UI using animation.
class ProductsViewController:... {
var inheritedProperty:UIView = targetView {
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
}
}
class AnimatedProductsViewController:ProductsViewController {
override var inheritedProperty:UIView {
//do something interesting if the property of super class changed
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
//you might want to call this method like so
// didSet { animate(newValue) }
}
func animate (view: UIView){
//do animation routine using UIView animation, UIDynamics, etc.
}
}
Property Observation
Whenever the didSelectCell... method is called just set a value to the inheritedProperty. Then add the property observers (see sample code) and react when the property changes (maybe pass a reference to the view you want to animate).
For example: Within the property observer you can just take that view and pass it to your animator function (whatever is going to do the animation). There are many examples on SO of how to animate a view so just search for (UIView animation, UIDynamics, etc).
The normal benefits of separation are encapsulation of functionality and reuse but Swift also guarantees that each set of property observers will fire independently. You'd have to give some more thought to this as to its applicability in this use case.
Do all this things in your viewController
Add target Method to cell's add button in cellForRowAtIndexPath Method
Like This
cell.add.addTarget(self, action: "addProduct:", forControlEvents: UIControlEvents.TouchUpInside)
Define method
func addProduct(button:UIButton)
{
// do button animation here
}

Resources