Design pattern for setAnimated and IBInspectable property in Swift - ios

I can’t figure out how to incorporate a design pattern in to my Swift class which has a property with the following requirements:
Property with default value and is IBInspectable
accompanying setProperty:animated: method
All of the ways I’ve tried require a separate private ‘instance' variable e.g. _property, like below:
#IBInspectable var progress: CGFloat = 0.5 {
didSet {
_progress = progress
setNeedsDisplay()
}
}
private var _progress: CGFloat = 0.5
func setProgress(progress: CGFloat, animated: Bool)
{
if _progress == progress
{
return
}
if animated
{
// Animate changes
// [Animation code]
}
else
{
// No need to animate changes
setNeedsDisplay()
}
titleLabel?.text = "\(NSInteger(progress * 100))%"
// Update value
_progress = progress
}
This doesn’t account for getting the property value though. As a property cannot have a get and didSet method.
So what is the correct way of having a property which is IBInspectable and can be set in the setAnimated method without automatically updating, bypassing the animation?

Related

Swift: Extension, All paths through this function will call itself [duplicate]

This question already has answers here:
Swift 4.2 Setter Getter, All paths through this function will call itself
(3 answers)
Why does Swift not allow stored properties in extensions?
(1 answer)
Closed 3 years ago.
I have a warning that I can not delete
All paths through this function will call itself
My code:
extension UIView {
var isLowAlpha: Bool {
get {
return self.isLowAlpha
}
set {
self.isUserInteractionEnabled = !newValue
self.alpha = newValue ? 0.3 : 1
}
}
}
I can not modify the code with this, because I have an error extension must not contain stored property ..:
extension UIView {
var isLowAlpha: Bool {
didSet {
self.isUserInteractionEnabled = !isLowAlpha
self.alpha = isLowAlpha ? 0.3 : 1
}
}
}
What is the solution?
One possible solution is to revert the process:
var isLowAlpha: Bool {
get {
return !isUserInteractionEnabled
}
set {
isUserInteractionEnabled = !newValue
alpha = newValue ? 0.3 : 1
}
}
or better, since you are not interested in the getter, make it a function:
func setIsLowAlpha(_ lowAlpha: Bool) {
self.isUserInteractionEnabled = !lowAlpha
self.lowAlpha = newValue ? 0.3 : 1
}
Anyway, looking at your code, you probably want to implement that a view is disabled. That's usually a task for UIControl subclasses, not UIView.
Also note the same can be implemented using a wrapper view:
class AlphaView: UIView {
var isLowAlpha: Bool = false {
didSet {
isUserInteractionEnabled = !isLowAlpha
alpha = isLowAlpha ? 0.3 : 1
}
}
}
And put your views inside.
In the first case you were recursively calling the variable's getter so it would never return. The way to fix this would be with a stored property _isLowAlpha, but unfortunately, as the second error mentions, Swift extensions cannot contain stored variables; they can only contain computed properties. If you really needed to add another property you would need to instead subclass UIView instead of making an extension.
However, in this case you can kind of cheat as long as you are not setting the UIView's alpha property elsewhere by directly using the alpha property:
extension UIView {
var isLowAlpha: Bool {
get {
return self.alpha == 0.3;
}
set {
self.alpha = newValue ? 0.3: 1
self.isUserInteractionEnabled = !newValue
}
}
}

Apply IBInspectable to all view types in iOS

I'm trying to apply IBInspectable to all types of view like UIView, UIButton, UITextField, UIImageView, etc.
Here's what I did:
#IBDesignable
class BaseView: UIView
{
override func layoutSubviews() {
super.layoutSubviews()
updateCornerRadius()
}
#IBInspectable var rounded: Bool = false {
didSet {
updateCornerRadius()
}
}
#IBInspectable var cornerRadius: CGFloat = 4 {
didSet {
updateCornerRadius()
}
}
private func updateCornerRadius() {
layer.cornerRadius = rounded ? cornerRadius : 0
}
}
It is working fine with all UIView using BaseView subclasses in storyboard, but how can I use this for buttons, text fields, image views, etc?
May be using protocols or extensions to avoid repeating this code to all other types of views...
You should use an extension instead of a subclass, and access the layer property directly:
extension UIView
{
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius;
}
set(value) {
layer.cornerRadius = value;
}
}
var rounded: Bool {
return layer.cornerRadius > 0.0;
}
}
Note: Since you're not implementing drawRect using #IBDesignable is needless. You can't implement roundedas a settable property this way, but you can have a read-only property for that.
It is the subClass of UIView. To use the these class you have to create subClass of UIImage, UIButton etc.
So create subclasses of each component like this
class ButtonSubClass: UIButton {}// for button subclass
class ImageSubClass: UIImage {}// for image subclass

Changing Values with WillSet Property Observer

I am attempting to create a UIView subclass. In the initialization, I set minimum and maximum y-axis origin values I want the subclass to have. This view will be moved up and down programmatically, by user gestures or through an animation. I was hoping I could use the willSet property user for this:
override var frame: CGRect {
willSet {
if newValue.origin.y > maximumOriginY {
self.frame.origin.y = maximumOriginY
}
if newValue.origin.y < minimumOriginY {
self.frame.origin.y = minimumOriginY
}
}
}
However, it doesn't seem to do anything. I have tried using DidSet property observer, but this resets the origin after it has been set, resulting in a stutter animation. Any ideas on how to get this to work with property observers or another way? Thanks
it is not possible to change the values within the willSet observer. overriding the getter and setter could work:
override var frame: CGRect {
get { return super.frame }
set {
var tempFrame = newValue
tempFrame.origin.y = min(max(tempFrame.origin.y, minimumOriginY), maximumOriginY)
super.frame = tempFrame
}
}

EXC_BAD_ACCESS Using IBInspectable

I am trying to use IBInspectable to add borders to my views.
extension UIView {
private func getBorder(integer: Int) -> UIRectEdge {
if integer == 1 {
return .top
} else if integer == 2 {
return .left
} else if integer == 3 {
return .right
} else if integer == 4 {
return .bottom
}
return .all
}
#IBInspectable var border: Int? {
get {
return self.border
}
set (value) {
self.border = value
for v in addBorder(edges: self.getBorder(integer: self.border!)) {
self.addSubview(v)
}
}
}
#IBInspectable var borderColor: UIColor? {
get {
return self.borderColor
}
set (value) {
self.borderColor = value //EXC_BAD_ACCESS here
for v in addBorder(edges: self.getBorder(integer: self.border!), color: borderColor!) {
self.addSubview(v)
}
}
}
private func addBorder(edges: UIRectEdge, color: UIColor = UIColor.white, thickness: CGFloat = 1) -> [UIView] {
...
}
}
The crash occurs on the line self.borderColor = value (in the set for the borderColor).
All it says in the debug log is (lldb). The crash itself says:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7fff53cc5fe8)
Here is my storyboard:
How can I fix this issue? Thanks!
You have an infinite recursion there, that is causing the crash. Basically within the setter of borderColor you're calling the setter for the same property, resulting the infinite recursion.
This happens because class extensions are not allowed to have stored properties, so Swift doesn't generate a backstore for your property, instead it treats it like a computed property, and calls the setter whenever you try to set the property.
There are two solutions that I can think of at this time, that will solve your problem:
Subclass UIView, add the two properties there, update the class in IB to match the name of your new class.
Use associated objects in your UIView accessors (objc_setAssociatedObject()/ objc_getAssociatedObject()) instead of direct iVar reference. You will not need to subclass and to update your xibs, however this solution is a little bit messier than the first one.

UIAppearance and Rounded Borders in Swift

So I'm fairly new to the whole UIAppearance approach to doing things, and doing it with swift. Hurray for not a ton of documentation out there.
I'm trying to set my border radius through UIAppearance with something along the lines of:
CircleButton.appearance.roundBorderRadius = 9
My CircleButton class implementation:
public class CircleButton : UIButton{
#nonobjc var roundBorderRadius: CGFloat? {
get { return self.layer.cornerRadius }
set {
self.layer.cornerRadius = newValue!
}
}
}
And I hook everything up in Storyboard to a ViewController that contains a CircleButton. No Compilation or Build errors.
However, at runtime I'm getting a:
"Thread 1: EXC_BAD_ACCESS" error on:
CircleButton.appearance.roundBorderRadius = 9
Any advice?
Remove #nonobjc and add dynamic, then change the type from CGFloat? to CGFloat and remove the ! after newValue in the setter, like so:
public class CircleButton: UIButton {
dynamic var roundBorderRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
}
}
}

Resources