How do you define a read-only property in Swift? I have one parent class which needs to define a public property eg. itemCount. Here's my code:
Class Parent: UIView {
private(set) var itemCount: Int = 0
}
class Child {
private(set) override var itemCount {
get {
return items.count
}
}
}
I get the error: Cannot override mutable property with read-only property
Option 1 - Protocols:
Well I can't use a protocol because they can't inherit from classes (UIView)
Option 2 - Composition:
I add a var view = UIView to my Child class and drop the UIView inheritance from my Parent class. This seems to be the only possible way, but in my actual project it seems like the wrong thing to do, eg. addSubview(myCustomView.view)
Option 3 - Subclass UIView on the Child class
I can't do this either because I intend to have multiple related Child classes with different properties and behaviour, and I need to be able to declare instances of my Child classes as the Parent class to take advantage of UIView's properties and Parent's public properties.
You can use a Computed Property which (like a method) can be overridden.
class Parent: UIView {
var itemCount: Int { return 0 }
}
class Child: Parent {
override var itemCount: Int { return 1 }
}
Update (as reply to the comment below)
This is how you declared and override a function
class Parent: UIView {
func doSomething() { print("Hello") }
}
class Child: Parent {
override func doSomething() { print("Hello world!") }
}
You can declare setter as private while getter is public.
public class someClass {
public private(set) var count: String
}
Refer to this link
As one more option you can use private variable for read/write and another for read-only. Count you're using for internal class changes, and numberOfItems for public access. Little bit weird, but it solves the problem.
class someClass {
private var count: Int = 0
var numberOfItems: Int { return count }
func doSomething() {
count += 1
}
}
Related
I have created my own property wrapper for the theming of UI components like UIView, UILabel etc.
class MyUIViewController: UIViewController {
#Theme private override var view: UIView! // it doesnt work!!!
#Theme private var myCustomView: UIView! // it works!!
}
in this case, i will get a compile error "Cannot override with a stored property 'view'"
I know that the view is a property of UIViewController. Do you know if there is any possible way to apply the property wrapper to a stored(superclass) property? any suggestions would be appreciated :) thanks a lot!
I found a way to do that but it's more like a hack than a good implementation (so I would not recommend it), and I haven't fully tested it (as it really on the UIViewController view loading mechanism, this can lead to some undefined behavior).
That said, in the property wrapper documentation you can find a "translation example" that explains how property wrapper works.
#Lazy var foo = 1738
// translates to:
private var _foo: Lazy<Int> = Lazy<Int>(wrappedValue: 1738)
var foo: Int {
get { return _foo.wrappedValue }
set { _foo.wrappedValue = newValue }
}
So we can imitate this to manually wrap a superclass property.
Note that doing this on the view property is a bit special as the view is not loaded during the view controller initialization, but more like a lazy var.
#propertyWrapper
struct Theme<WrappedValue: UIView> {
var wrappedValue: WrappedValue?
}
class Controller: UIViewController {
override func loadView() {
super.loadView()
_view.wrappedValue = view
}
private var _view: Theme<UIView> = .init()
override var view: UIView! {
get {
if _view.wrappedValue == nil {
// This is a trick I would not recommend using, but basically this line
// forces the UIViewController to load its view and trigger the
// loadView() method.
_ = super.view
}
return _view.wrappedValue
}
set {
_view.wrappedValue = newValue
}
}
}
I made the wrapped value in the property wrapper optional because the view property is nil during the initialization process (as the view is not yet loaded)
So I wrote a simple protocol:
protocol PopupMessageType{
var cancelButton: UIButton {get set}
func cancel()
}
and have a customView:
class XYZMessageView: UIView, PopupMessageType {
...
}
and then I currently have:
class PopUpViewController: UIViewController {
//code...
var messageView : CCPopupMessageView!
private func setupUI(){
view.addSubview(messageView)
}
}
But what I want to do is:
class PopUpViewController: UIViewController {
//code...
var messageView : PopupMessageType!
private func setupUI(){
view.addSubview(messageView) // ERROR
}
}
ERROR I get:
Cannot convert value of type 'PopupMessageType!' to expected argument
type 'UIView'
EDIT:
I'm on Swift 2.3!
Change the type of property messageView to (UIView & PopupMessageType)!
I mean
class PopUpViewController: UIViewController {
//code...
var messageView : (UIView & PopupMessageType)!
private func setupUI(){
view.addSubview(messageView) // ERROR
}
}
In Swift 4 you can do this:
typealias PopupMessageViewType = UIView & PopupMessageType
And then use PopupMessageViewType as the type of the variable.
DISCLAIMER: I do not have the swift 2.3 compiler anymore since swift 4 is the new normal for iOS development. The following code may possibly need tweaks to get it working in swift 2.3
Essentially we will be making a 2x1 mux where the two inputs are the same object. The output depends on whether you set the mux to choose the first or the second one.
// The given protocol
protocol PopupMessageType{
var cancelButton: UIButton {get set}
func cancel()
}
// The object that conforms to that protocol
class XYZMessageView: UIView, PopupMessageType {
var cancelButton: UIButton = UIButton()
func cancel() {
}
}
// The mux that lets you choose the UIView subclass or the PopupMessageType
struct ObjectPopupMessageTypeProtocolMux<VIEW_TYPE: UIView> {
let view: VIEW_TYPE
let popupMessage: PopupMessageType
}
// A class that holds and instance to the ObjectPopupMessageTypeProtocolMux
class PopUpViewController: UIViewController {
var messageWrapper : ObjectPopupMessageTypeProtocolMux<UIView>!
private func setupUI(){
view.addSubview(messageWrapper.view)
}
}
//...
let vc = PopUpViewController() // create the view controller
let inputView = XYZMessageView() // create desired view
// create the ObjectPopupMessageTypeProtocolMux
vc.messageWrapper = ObjectPopupMessageTypeProtocolMux(view: inputView, popupMessage: inputView) //<-- 1
vc.messageWrapper.view // retreive the view
vc.messageWrapper.popupMessage.cancel() // access the protocol's methods
vc.messageWrapper.popupMessage.cancelButton // get the button
1) I input the "inputView" twice for the initializer of ObjectPopupMessageTypeProtocolMux. They are the same class instance, but they get casted to different types.
I hope this helps you get to where you wanna go in swift 2.3
In my two classes, I have the following code
// Two classes who's code I am trying to reduce using protocols
class class1 {
var view: viewSubclass1!
func printName() {
print("This is the view: \(view)")
}
}
class class2 {
var view: viewSubclass2!
func printName() {
print("This is the view: \(view)")
}
}
// The protocol and protocol extension I am trying to create
protocol myProtocol {
var view: UIView {get set}
func printName()
}
extension myProtocol {
func printName() {
print("This is the view: \(view)")
}
}
// The subviews of UIView
class viewSubclass1: UIView {}
class viewSubclass2: UIView {}
As you can see, class1 and class2 are different classes, but have the same variable name: view and the same function names. The difference is that the view variable are of different types.
Question: Using protocols and protocol-extensions, how can I reduce the code between the two classes? I would not want to do repeat code because they are so similar. I have been trying different variations of the code, but I keep getting stuck. I do not understand it well. How can i get both classes to use the same code using a protocol, and a protocol extension?
(Updated)
With declaring the protocol and protocol extension:
protocol MyProtocol {
associatedtype BaseView: UIView
var view: BaseView! {get set}
func printName()
}
extension MyProtocol {
func printName() {
print("This is the view: \(view)")
}
}
And given two subclasses of UIView:
class ViewSubclass1: UIView {}
class ViewSubclass2: UIView {}
(Capitalized some type names for readability.)
And declaring two classes like this:
class Class1: MyProtocol {
var view: ViewSubclass1!
}
class Class2: MyProtocol {
var view: ViewSubclass2!
}
You'll find you can use your printName() for both Class1 and Class2.
One thing important here:
If you declare your view in the protocol as var view: UIView {get set}, var view: ViewSubclass1! cannot fulfill the requirement for the protocol.
(One thing is the difference of optionality, another is that you cannot assign any instances of UIView or UIView's subclasses to view: ViewSubclass1.)
But many things depend on what you really want to do in your common code.
I have created a custom UISegment Control
#IBDesignable class CardsSegmentedControl: UIControl {
private var labels = [UILabel]()
var thumbView = UIView()
var items: [String] = ["Saved Cards", "Add Card"] {
didSet {
setupLabels()
}
}
var selectedIndex : Int = 0 {
didSet {
displayNewSelectedIndex()
}
}
....
}
Now I wish to change the value of the variable selectedIndex in the viewController where I am adding this custom segment control in.
I guess it is a problem of how to access/change variables from another class.
I tried to create a class func which would set the value of the selectedIndex but I cannot get it to access the selectedIndex variable either.
Still pretty new to Swift so please bear with me.
// Inside your ViewController class, create a new instance of your custom class
var cardSegmentedControl = CardSegmentedControl()
// here, change its property value
cardSegmentedControl.selectedIndex = 1
In Swift, how can you invoke a subclass's function in the base class's init method? Essentially, the goal is to ensure each subclass invokes its own version of initGrid, and then all the code to set numRows and numCols is defined only once, in the base class.
However, when initializing a subclass, the initGrid function from the base class -- not the subclass -- is run instead. This results in an array index exception since grid is empty unless the subclass creates it.
class BaseGrid {
var grid = [[NodeType]]()
var numRows = 0
var numCols = 0
init() {
// Init grid
initGrid()
// Set <numRows>
numRows = grid.count
// Set <numCols>
numCols = grid[0].count
// Verify each row contains same number of columns
for row in 0..<numRows {
assert(grid[row].count == numCols)
}
}
// Subclasses override this function
func initGrid() {}
}
class ChildGrid: BaseGrid {
override init() {
super.init()
}
override func initGrid() {
grid = [
[NodeType.Blue, NodeType.Blue, NodeType.Blue],
[NodeType.Red, NodeType.Red, NodeType.Red],
[NodeType.Empty, NodeType.Blue, NodeType.Empty]
]
}
}
if you will subclass and override initGrid, it will call method in current scope.
In base class, you can stay it empty, as abstract.
I.e.:
class AdvancedGrid: BaseGrid {
override init() {
super.init()
}
override func initGrid() {
// here is you custom alghoritm
}
}