Apply property wrapper in superclass owned property - ios

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)

Related

Instantiate the delegate pattern one time or more in Swift?

I am studying delegate pattern for swift on below code. I can not be pretty sure how can I use this option "without the need to reinstantiate the view.
protocol ShapeViewDelegate {
func drawShapeView(_ shapeView: ShapeView)
}
class ShapeView: UIView {
var strokeColor: UIColor?
var fillColor: UIColor?
var delegate: ShapeViewDelegate? {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
delegate?.drawShapeView(self) // self means object( ShapeView() ) is it instantiated ?
}
}
View object supposed to ready coming from delegate object but I didn't instantiate it, where this object instantiated using protocol automatically instantiated it at the run time. So I am writing an example like this :
class ShapeViewController: ShapeViewDelegate {
drawShapeView(view)
}
View is instantiated in other words occupied the memory at this example ?
You have to define drawShapeView() in ShapeViewController. In your code you are using or calling drawShapeView() which is already being called in your ShapeView. And about instantiate, yes you need to instantiate ShapeView in ShapeViewController or any other place you are confirming the delegate.
Code in your ShapeViewController -
class ShapeViewController: ShapeViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let shape = ShapeView(...)
shape.delegate = self
view.addSubView(shape)
}
func drawShapeView(_ shapeView: ShapeView) {
//your code here
}
}
Via this feature, you can have many definitions of ShapeView based on different ShapeViewController instances without worrying to modify ShapeView.

How do i declare a variable of type NSLayoutConstraint in an extension in swift

I have created an extension for my UIViewController and I want to declare some variables which I can use through out the extension. Below is my code
extension UIViewController {
var mssgTopConstraint1: NSLayoutConstraint?
var mssgTopConstraint2: NSLayoutConstraint?
var mssgIconTop1: NSLayoutConstraint?
var mssgIconTop2: NSLayoutConstraint?
var isCheviClicked: Bool = false
}
I have this error extension may not contain stored properties for all five variables.
The error is pretty clear, you cannot add stored properties within an extension, you can only use computed properties.
You can subclass UIViewController and declare those properties within your custom class:
class MyViewController: UIViewController {
var mssgTopConstraint1: NSLayoutConstraint?
var mssgTopConstraint2: NSLayoutConstraint?
var mssgIconTop1: NSLayoutConstraint?
var mssgIconTop2: NSLayoutConstraint?
var isCheviClicked: Bool = false
}
This is a cleaner approach, as extending UIViewController with those properties doesn't makes too much sense as not all controllers need those properties.
You can instantiate this new controller, or set the class name in XIB/Storyboard to be MyViewController. This way you can use the new controller and the newly declared properties.
Don't implement getter and setter directly while declaring a variable into an extension you have to create a holder otherwise it will create a retain cycle here is an example.
extension UIViewController {
struct Holder {
static var contraint: NSLayoutConstraint = NSLayoutConstraint()
}
var actualVariable: NSLayoutConstraint {
get {
return Holder.contraint
}
set {
Holder.contraint = newValue
}
}
}
Happy coding

How to add protocol type as subview

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

Passing data between views in ONE ViewController in Swift

All of the searches I've done focus on passing data between view controllers. That's not really what I'm trying to do. I have a ViewController that has multiple Views in it. The ViewController has a slider which works fine:
var throttleSetting = Float()
#IBAction func changeThrottleSetting(sender: UISlider)
{
throttleSetting = sender.value
}
Then, in one of the Views contained in that same ViewController, I have a basic line that (for now) sets an initial value which is used later in the DrawRect portion of the code:
var RPMPointerAngle: CGFloat {
var angle: CGFloat = 2.0
return angle
}
What I want to do is have the slider's value from the ViewController be passed to the View contained in the ViewController to allow the drawRect to be dynamic.
Thanks for your help!
EDIT: Sorry, when I created this answer I was having ViewControllers in mind. A much easier way would be to create a method in SomeView and talk directly to it.
Example:
class MainViewController: UIViewController {
var view1: SomeView!
var view2: SomeView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the views here
view1 = SomeView()
view2 = SomeView()
view.addSubview(view1)
view.addSubview(view2)
}
#IBAction func someAction(sender: UIButton) {
view1.changeString("blabla")
}
}
class SomeView: UIView {
var someString: String?
func changeString(someText: String) {
someString = someText
}
}
Delegate:
First you create a protocol:
protocol NameOfDelegate: class { // ": class" isn't mandatory, but it is when you want to set the delegate property to weak
func someFunction() // this function has to be implemented in your MainViewController so it can access the properties and other methods in there
}
In your Views you have to add:
class SomeView: UIView, NameOfDelegate {
// your code
func someFunction() {
// change your slider settings
}
}
And the last step, you'll have to add a property of the delegate, so you can "talk" to it. Personally I imagine this property to be a gate of some sort, between the two classes so they can talk to each other.
class MainViewController: UIViewController {
weak var delegate: NameOfDelegate?
#IBAction func button(sender: UIButton) {
if delegate != nil {
let someString = delegate.someFunction()
}
}
}
I used a button here just to show how you could use the delegate. Just replace it with your slider to change the properties of your Views
EDIT: One thing I forgot to mention is, you'll somehow need to assign SomeView as the delegate. But like I said, I don't know how you're creating the views etc so I can't help you with that.
In the MVC model views can't communicate directly with each other.
There is always a view controller who manages the views. The views are just like the controllers minions.
All communication goes via a view controller.
If you want to react to some view changing, you can setup an IBAction. In the method you can then change your other view to which you might have an IBOutlet.
So in your example you might have an IBAction for the slider changing it's value (as in your original question) from which you could set some public properties on the view you would like to change. If necessary you could also call setNeedsDisplay() on the target view to make it redraw itself.

Overriding delegate property of UIScrollView in Swift (like UICollectionView does)

UIScrollView has a delegate property which conforms to UIScrollViewDelegate
protocol UIScrollViewDelegate : NSObjectProtocol {
//...
}
class UIScrollView : UIView, NSCoding {
unowned(unsafe) var delegate: UIScrollViewDelegate?
//...
}
UICollectionView overrides this property with a different type UICollectionViewDelegate
protocol UICollectionViewDelegate : UIScrollViewDelegate, NSObjectProtocol {
//...
}
class UICollectionView : UIScrollView {
unowned(unsafe) var delegate: UICollectionViewDelegate?
//...
}
When I try to override UIScrollViews delegate with my protocol like so:
protocol MyScrollViewDelegate : UIScrollViewDelegate, NSObjectProtocol {
//...
}
class MyScrollView: UIScrollView {
unowned(unsafe) var delegate: MyScrollViewDelegate?
}
the compiler gives me two warnings:
Property 'delegate' with type 'MyScrollViewDelegate?' cannot override a property with type 'UIScrollViewDelegate?'
'unowned' cannot be applied to non-class type 'MyScrollViewDelegate?'
How can I subclass UIScrollView and override type of delegate property (i.e. use a custom delegate protocol) ?
I think overriding an inherited property is something that's possible in Objective-C but not (at least currently) in Swift. The way I've handled this is to declare a separate delegate as a computed property of the correct type that gets and sets the actual delegate:
#objc protocol MyScrollViewDelegate : UIScrollViewDelegate, NSObjectProtocol {
func myHeight() -> CGFloat
// ...
}
class MyScrollView: UIScrollView {
var myDelegate: MyScrollViewDelegate? {
get { return self.delegate as? MyScrollViewDelegate }
set { self.delegate = newValue }
}
}
This way anything that calls the scroll view delegate normally still works, and you can call your particular delegate methods on self.myDelegate, like this:
if let height = self.myDelegate?.myHeight() {
// ...
}
You can do like this:
protocol ExtendedUIScrollViewDelegate: UIScrollViewDelegate {
func someNewFunction()
}
class CustomScrollView: UIScrollView {
weak var myDelegate: ExtendedScrollViewDelegate?
override weak var delegate: UIScrollViewDelegate? {
didSet {
myDelegate = delegate as? ExtendedScrollViewDelegate
}
}
}
Hope this helps
My favoured method personally is not to subclass scrollviews directly but to make a UIView subclass containing and acting as delegate for a separate scrollview, then forward that scrollview's delegate messages on to the UIView subclass's own delegate where necessary. This also allows for the adding of custom controls outside of the area defined by the scroll view. It may seem a little inelegant compared to a direct subclass, but it does at least avoid unpleasant hacks.
Here is a solution for changing the type of the overriding properties in Swift. It is especially useful when you need to extend protocols of delegates.
#objc protocol ExtendedUIScrollViewDelegate: UIScrollViewDelegate {
func someNewFunction()
}
class CustomScrollView: UIScrollView {
weak var delegateInterceptor: ExtendedScrollViewDelegate?
override var delegate: UIScrollViewDelegate! {
didSet {
if let newValue = delegate {
let castedDelegate = unsafeBitCast(delegate, ExtendedScrollViewDelegate.self)
delegateInterceptor = castedDelegate
}
else {
delegateInterceptor = nil
}
}
}
}
This works as tested with Swift version 1.2. I hope it helps.
You can override get and set method by declare function like:
func setDelegate(delegate:UITableViewDelegate?){
self.delegateInterceptor = delegate;
}
swift compiler the property to method as Objective-c does.
Consider the following situation:
class BaseProp {}
class Base {
var prop: BaseProp
}
Then if you do this:
class DerivedProp: BaseProp {}
class Derived: Base {
override var prop: DerivedProp
}
Then if would break the subclassing principles (namely, the Liskov Substitution Principle). Basically what you are doing is limiting the scope of "var prop" from wider "BaseProp" type to a more narrow "DerivedProp" type. Then this kind of code would be possible, which does not make sense:
class UnrelatedProp: BaseProp {}
let derived = Derived()
let base = derived as Base
base.prop = UnrelatedProp()
Note that we are assigning an instance of UnrelatedProp to the property, which does not make sense for the Derived instance which we actually operate with. ObjectiveC allows such kind of ambiguity, but Swift doesn't.

Resources