Is this officially the proper way of using get and set in Swift? - swift

Let's say for example I want to make some kind of a radio button which keeps track of its active state and changes color when its state is changed. I want it to change color as I set the value. This is how I would implement it:
class TagButton: UIButton {
var _active: Bool = false
var active: Bool {
set(newVal){
_active = newVal
if(!newVal){
self.backgroundColor = UIColor.white //inactive
}
else {
self.backgroundColor = UIColor.red //active
}
}
get {
return _active
}
}
}
Now, I have seen some questions suggest a similar approach, but what bothers me, is whether or not this is actually intended use of Swift. I have a feeling I am inventing a bicycle here. And I could not find anything about this in official Swift documentation. Was anyone able to confirm this?

Your code looks like Objective-C. In Swift, there is no need to create the backing storage and you can use the property observer didSet to change the background color:
class TagButton: UIButton {
var active = false {
didSet {
backgroundColor = active ? .red : .white
}
}
}
Or you could use a Computed Property and not have storage for active at all:
class TagButton: UIButton {
var active: Bool {
set {
backgroundColor = newValue ? .red : .white
}
get {
return backgroundColor == .red
}
}
}
You can read more about Property Observers and Computed Properties here.

Related

Set a bool value using model object didSet

I'm struggling to figure out how to properly set bool values using a model object's didSet. My app has a series of swipable cards where some flip and some don't. This code below is the CardView which is run for each card created.
Currently, the code works perfectly for the image and label—each card loads unique information based each card's model object. However, the button and isFlippable property are where I'm struggling.
The code right now is always loading the green pathway. The weird thing, however, is that even when the cardModel should sets the button isEnabled to false, it will still load the green (but the button won't work, so it did become disabled...)
var cardModel: CardModel! {
didSet {
imageView.image = cardModel.image
label.text = cardModel.label
flipButton.isEnabled = cardModel.isFlippable
isBackShowing = cardModel.isFlippable //Intentionally use isFlippable here because I want the initial layout to be based on this true or false value.
}
}
let imageView = UIImageView()
let label = UILabel()
let flipButton = UIButton()
var isBackShowing = false
override init(frame: CGRect) {
super.init(frame: frame)
setupLayout()
}
fileprivate func setupLayout() {
if flipButton.isEnabled == true {
if isBackShowing == true {
backgroundColor = .red
} else {
backgroundColor = .green
}
} else {
backgroundColor = .yellow
}
}
I also have code for when the button flips that alternates "isBackShowing" and then calls setupLayout()—it is working fine. But it always loads as false during the initial setup of the card.
For better readability you can little bit update your code replacing var isBackShowing = Bool() by var isBackShowing = false.
And also you can call setupLayout() to update your layout after setting of cardModel. For example didSet of cardModel can looks like this:
var cardModel: CardModel! {
didSet {
imageView.image = cardModel.image
label.text = cardModel.label
flipButton.isEnabled = cardModel.isFlippable
isBackShowing = cardModel.isFlippable
setupLayout()
}
}

How to create block screen with circle loader

I am doing an app that does background job that can take some time
I want to show a loader in that time
I want a black screen with a simple loader in the front of it
and show it \ hide it,
when I do actions in the background
I want to do a simple half black square with loader circle
that also blocks presses to the screen
Like in this picture:
How can I achieve that and that ?
First create one UIView which you will put in front of your LogIn view. Then add UIActivityIndicatorView to the created UIView.
let loadingIndicatorView = UIView()
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
Now the loadingIndicatorView should have same frame size as your LogIN view. For color you can set your own color with alpha as you want to show LogIn content too. Initially keep it hidden and whenever you want to show it unhide it.
loadingIndicatorView.frame = view.frame
loadingIndicatorView.backgroundColor = .gray
loadingIndicatorView.isHidden = true
Now setup activityIndicatorView, it should be shown at centre,
activityIndicatorView.center = CGPoint(
x: UIScreen.main.bounds.size.width / 2,
y: UIScreen.main.bounds.size.height / 2
)
You can set some color to the indicator,
activityIndicatorView.color = .white
activityIndicatorView.hidesWhenStopped = true
Now add this activityIndicatorView to loadingIndicatorView and loadingIndicatorView to LogIn View.
loadingIndicatorView.addSubview(activityIndicatorView)
view.addSubview(loadingIndicatorView)
Lastly for showing do,
loadingIndicator.startAnimating()
loadingIndicatorView.isHidden = false
And for hiding,
loadingIndicator.stopAnimating()
loadingIndicatorView.isHidden = true
Updated Answer
Since the OP wanted an example code. Hence the updated answer. Hope everyone gets to learn something or the other out of it.
To start with, I created a subclass of UIView and named it PSOverlaySpinner and it looks something like below:
import UIKit
class PSOverlaySpinner: UIView {
//MARK: - Variables
private var isSpinning: Bool = false
private lazy var spinner : UIActivityIndicatorView = {
var spinner = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.white)
spinner.translatesAutoresizingMaskIntoConstraints = false
spinner.hidesWhenStopped = true
return spinner
}()
// MARK: - View Lifecycle Functions
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
super.init(frame: CGRect.zero)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = UIColor.init(white: 0.0, alpha: 0.8)
self.isSpinning = false
self.isHidden = true
createSubviews()
}
deinit {
self.removeFromSuperview()
}
func createSubviews() -> Void {
self.addSubview(spinner)
setupAutoLayout()
}
// MARK: - Private Methods
private func setupAutoLayout() {
if #available(iOS 11.0, *) {
spinner.safeAreaLayoutGuide.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor).isActive = true
spinner.safeAreaLayoutGuide.centerYAnchor.constraint(equalTo: safeAreaLayoutGuide.centerYAnchor).isActive = true
} else {
// Fallback on earlier versions
spinner.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
spinner.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
// MARK: - Public Methods
public func show() -> Void {
DispatchQueue.main.async {
if !self.spinner.isAnimating {
self.spinner.startAnimating()
}
self.isHidden = false
}
isSpinning = true
}
public func hide() -> Void {
DispatchQueue.main.async {
if self.spinner.isAnimating {
self.spinner.stopAnimating()
}
self.isHidden = true
}
isSpinning = false
}
}
Now move onto the ViewController that you want to add this overlay view to. Since I create my views programmatically, I will show how to do it the same way, but you can easily do it via storyboard or xibs.
Step 1 : Initialize
public lazy var spinnerView : PSOverlaySpinner = {
let loadingView : PSOverlaySpinner = PSOverlaySpinner()
return loadingView
}()
Step 2 : Add as a subview
self.view.addSubview(spinnerView)
Step 3 : Set constraints
spinnerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
spinnerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
spinnerView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
spinnerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
Step 4 : To show PSOverlaySpinner
spinnerView.show()
Step 5 : To hide PSOverlaySpinner
spinnerView.hide()
That is it!!
If you want you can go ahead and modify the PSOverlaySpinner as per your needs. For example, you might want to add a UILabel below the spinner indicating him of the type of action taking place and so on.
Before
After
Old Answer
If you wish to do it manually then create a UIView with the its frame matching self.view.bounds, with 0.5-0.7 alpha and black background color. Add UIActivityIndicator as its subview constrained to its center. For a spinner specific to the image you will have to use the open sourced spinners made available. A couple of them can be found here. Once done add this view as the topmost subview in self.view.
You need to import this library SVProgressHUD and then set few properties like as follows:
SVProgressHUD.setDefaultStyle(SVProgressHUDStyle.dark)
SVProgressHUD.setBackgroundColor(.clear)
SVProgressHUD.setForegroundColor(.white)
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.show()
//SVProgressHUD.show(withStatus: "Loading something, Loading something,Loading something ...")
This will produce same UI output as needed by you in OP. You can find a running sample at my repository (TestPreLoader)

Overriding isHighlighted still changes UIControlState - why?

In a UIControl, if I override isHighlighted to set a private _isHighlighted property, and then check the control's state to see if it contains .highlighted, the state still accurately reflects the change. See code below.
My question is, how is this possible? I never call super.isHighlighted or manipulate the state property. state is an OptionSet that must have the .highlighted property inserted into the set, which, from what I can determine, does not, or should not, happen if I override the property.
The only other explanation I can think of is that state is actually a computed property based off other properties (i.e. isSelected, isEnabled, etc.)
class MyControl: UIControl {
private var _isHighlighted: Bool = false
override var isHighlighted: Bool {
get { return self._isHighlighted }
set { self._isHighlighted = newValue }
}
}
let myControl = MyControl()
myControl.isHighlighted = true
myControl.state.contains(.highlighted) // returns true
The only other explanation I can think of is that state is actually a computed property based off other properties (i.e. isSelected, isEnabled, etc.)
Good explanation! Let's try logging (printing) in the getter to see if that's true:
class MyControl: UIControl {
private var _isHighlighted: Bool = false
override var isHighlighted: Bool {
get { print("getting"); return self._isHighlighted }
set { self._isHighlighted = newValue }
}
}
let myControl = MyControl()
myControl.isHighlighted = true
print("about to check state")
myControl.state.contains(.highlighted)
print("checked state")
And here's the log:
about to check state
getting
checked state
Quod erat demonstrandum.

When to use didset or get set when using #IBInspectable

After looking at different tutorial. i don't know when to use didset or get set to update the variable.
Could anyone explain a little bit more detail for when to use didset or get set?
#IBInspectable var circleColor: UIColor = UIColor.redColor() {
didSet { //after properties are set in storyboard, update here
circleLayer.strokeColor = circleColor.CGColor
self.toggleButon()
}
}
/**
Radius of RadioButton circle.
*/
#IBInspectable var circleRadius: CGFloat = 5.0
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
for circle radius, it doesn't have to use didset to update its value. i can't get it.
Here i am giving you one example and try to explain you how to use, hope it will helpful for you.
I am using this class for UIView here for did set and get set with Story Board my class name here "MyCustomView"
import Foundation
import UIKit
import QuartzCore
/// Computed properties, based on the backing CALayer property, that are visible in Interface Builder.
#IBDesignable public class MyCustomView: UIView {
/// When positive, the background of the layer will be drawn with rounded corners. Also effects the mask generated by the `masksToBounds' property. Defaults to zero. Animatable.
#IBInspectable var cornerRadius: Double {
get {
return Double(self.layer.cornerRadius)
}
set {
self.layer.cornerRadius = CGFloat(newValue)
}
}
/// The width of the layer's border, inset from the layer bounds. The border is composited above the layer's content and sublayers and includes the effects of the `cornerRadius' property. Defaults to zero. Animatable.
#IBInspectable var borderWidth: Double {
get {
return Double(self.layer.borderWidth)
}
set {
self.layer.borderWidth = CGFloat(newValue)
}
}
/// The color of the layer's border. Defaults to opaque black. Colors created from tiled patterns are supported. Animatable.
#IBInspectable var borderColor: UIColor? {
get {
return UIColor(CGColor: self.layer.borderColor!)
}
set {
self.layer.borderColor = newValue?.CGColor
}
}
/// The color of the shadow. Defaults to opaque black. Colors created from patterns are currently NOT supported. Animatable.
#IBInspectable var shadowColor: UIColor? {
get {
return UIColor(CGColor: self.layer.shadowColor!)
}
set {
self.layer.shadowColor = newValue?.CGColor
}
}
/// The opacity of the shadow. Defaults to 0. Specifying a value outside the [0,1] range will give undefined results. Animatable.
#IBInspectable var shadowOpacity: Float {
get {
return self.layer.shadowOpacity
}
set {
self.layer.shadowOpacity = newValue
}
}
/// The shadow offset. Defaults to (0, -3). Animatable.
#IBInspectable var shadowOffset: CGSize {
get {
return self.layer.shadowOffset
}
set {
self.layer.shadowOffset = newValue
}
}
/// The blur radius used to create the shadow. Defaults to 3. Animatable.
#IBInspectable var shadowRadius: Double {
get {
return Double(self.layer.shadowRadius)
}
set {
self.layer.shadowRadius = CGFloat(newValue)
}
}
}
And you can use this with story board import this class with your "UIView"
after you will see some
and you directly set here view cornet radius, shadow and Shadow
and result you can see inside your storyboard directly without running code
Output here from this code
This answer explains the difference in usage of set vs didSet quite clearly.
IN SUMMARY
willSet should be used for doing something before the value is set. (the value is not updated at this point).
set is to update the value
didSet if you want to do anything after the value is set (the value has been updated at this point)
ALSO
if you implement set, you will also need to implement get
but didSet can also be used without having to implement any other method
#IBInspectable will work with both property kinds:
Use didSet{} for stored properties:
didSet is a property observer.
Use set{} get{} for computed properties.
in the following example: firstName And lastName are stored properties.
fullName is a Computed property:
struct Person{
var firstName:String
var lastName:String{
didSet{
//property observer
print("It was set")
}
}
var fullName:String{
get{
return "\(firstName)-\(lastName)"
}
set{
let split = newValue.split(separator: "-")
firstName = String(split.first ?? "")
lastName = String(split.last ?? "")
}
}
}
var p = Person(firstName: "Moshe", lastName: "Doe")
print(p.firstName)
print(p.lastName)
print(p.fullName)
p.fullName = "Jhon-Doe"
print(p.firstName)
print(p.lastName)
print(p.fullName)
Also look into the language guide: (Properties)
https://docs.swift.org/swift-book/LanguageGuide/Properties.html
One final note about properties and #IBInspectable:
Validation of a value may be achieved using a combo of a computed property with a stored property (Backing property): Here is an example:
//Bound between 0 and 5
var boundRating:Int = 3{
didSet{
renderRating()
}
}
#IBInspectable
var rating:Int{
set{
if newValue <= 5 && newValue >= 0{
boundRating = newValue
renderRating()
}
}
get{
return boundRating
}
}

Is it possible use non read-only computed property in extension?

Is it possible for a computed property in extension to have both getter and setter? Apple's guide does not mention it and the only example I have seen only shows read-only computed property in extension.
Is it possible computed property in extension that has getter and setter?
Yes.
Probably one of the most common uses of computed properties in extensions in my experience is providing a wrapper to make easier access to particular properties.
For example, when we want to modify the border layer, border color, or corner radius of anything out of UIKit, we're stuck going through the layer property.
But we can extend UIView with a property with both a setter & getter to provide a much more convenient means of changing the properties of its layer:
extension UIView {
var borderColor: UIColor? {
get {
guard let color = self.layer.borderColor else {
return nil
}
return UIColor(CGColor: color)
}
set {
self.layer.borderColor = newValue?.CGColor
}
}
}
Moreover, if we really want to, we can leverage the Objective-C run time to emulate stored properties in extensions (which of course mean setting & getting). Take part of this Stack Overflow answer for example:
private var kAssociationKeyNextField: UInt8 = 0
extension UITextField {
#IBOutlet var nextField: UITextField? {
get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
}
set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
}
}
}
This serves as just one example of a property in an extension with a setter & getter.
This works:
extension Bool
{
public var integerValue: Int
{
get
{
return true ? 1 : 0
}
set
{
self = (newValue > 0) ? true : false
}
}
}
So yes.