Instantiate the delegate pattern one time or more in Swift? - ios

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.

Related

Delegating action through protocol not working swift

I needed to delegate a click action for my UIView class to my UIViewController class since Swift does not support multiple class inheritance. So i wanted it such that once a button is clicked on my subview, a function in my BrowserViewController class is called.
I am using a protocol to achieve this, but on the function does not triggered when the button is tapped. Please help me out.
View Controller
class BrowseViewController: UIViewController {
var categoryItem: CategoryItem! = CategoryItem() //Category Item
private func setupExplore() {
//assign delegate of category item to controller
self.categoryItem.delegate = self
}
}
// delegate function to be called
extension BrowseViewController: ExploreDelegate {
func categoryClicked(category: ProductCategory) {
print("clicked")
let categoryView = ProductByCategoryView()
categoryView.category = category
categoryView.modalPresentationStyle = .overCurrentContext
self.navigationController?.pushViewController(categoryView, animated: true)
}
}
Explore.swift (subview)
import UIKit
protocol ExploreDelegate: UIViewController {
func categoryClicked(category: ProductCategory)
}
class Explore: UIView {
var delegate: ExploreDelegate?
class CategoryItem: UIView {
var delegate: ExploreDelegate?
var category: ProductCategory? {
didSet {
self.configure()
}
}
var tapped: ((_ category: ProductCategory?) -> Void)?
func configure() {
self.layer.cornerRadius = 6
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.categoryTapped)))
self.layoutIfNeeded()
}
#objc func categoryTapped(_ sender: UIGestureRecognizer) {
delegate?.categoryClicked(category: ProductCategory.everything)
self.tapped?(self.category)
}
}
}
Simply add a print statement inside categoryTapped.
You will then know if it is actually being tapped.
A million things could go wrong, for example, you may have forget to set the UIView to allow intertaction.
After checking that. Next add another print statement inside categoryTapped which shows you whether or not the delegate variable is null.
You'll quickly discover the problem using simple print statements.
print("I got to here!")
It's that easy.
And what about
if delegate == nil { print("it is nil!! oh no!" }
else { print("phew. it is NOT nil.") }
Debugging is really that easy at this level.
Next add a print statement inside setupExplore()
func setupExplore() {
print("setup explore was called")
....
See what happens.
I don't see any piece of code which sets the delegate.
First of all, define delegate as a property inside CategoryItem class, Then you must set the current instance of BrowseViewController to the delegate variable of CategoryItem. Now you can expect your method being called.
There are a few things that could cause the delegate method to not be triggered in this code:
Ensure that isUserInteractionEnabled = true on your CategoryItem. This is probably best done in either the configure() function in the CategoryItem or in the setupExplore() function of the BrowseViewController.
Make sure that the setupExplore() function on the BrowseViewController is being called, and that the category is being set on the CategoryItem to trigger the configure function. Otherwise, either the delegate or the gesture recognizer might not being set.
Side Note - weak vs strong delegate
On a side note, it is usually best practice to make your delegate properties weak var rather that having them be a strong reference, as this makes them prone to strong retain cycles.
Therefore, you might want to consider making the var delegate: ExploreDelegate? on your CategoryItem into weak var delegate: ExploreDelegate?. For more information on this problem, view this post.

Alternative to override extension's method

I want to extend UIView by adding some functions, and override them in any subclass of UIView that I want. I found in apple documentations that I can't override extensions (and the compiler will complain) which make some sense. So
I need someone to suggest an alternative way to the below:
extension UIView {
func hide() { //do almost nothing }
}
class myLabel: UILabel {
override func hide() {
//do work on uilabel that can't be done on imgView
}
}
class myImageView: UIImageView {
override func hide() {
//do work on imgView that can't be done on uilabel
}
}
And the reason I want this is that later in my code I will face the below code and I have to many subclasses and I don't want to write too many if-lets trying to cast the view to myLabel, myTextView, myImageView... etc
let view = cell.viewWithTag(someTag)
// and I want to write this below without casting
view.hide()
I tried with protocols and protocol extensions but I couldn't make it though.
Any thoughts?
Note: func hide() is just an example. My func will have more to do.
**Question updated to be clear.
EDIT: Updating answer to make use of protocols also
Protocols does in various ways enable to you replace subclassing in some cases however you still need your class to conform to the protocol to be able to see and override those methods
You can have a protocol for example:
protocol SomeProtocol {
func hide()
}
To do what you are intending to do it is best to have a parent subclass UIView with all functions that can be overridden for example (in this updated answer you can have your methods to override inside the protocol and have your subclasses conform to it):
class ParentView : UIView, SomeProtocol {
func hide() {
print("PARENT")
}
func anyOtherMethod() {
}
}
and then have all the other UIView's that need to override those methods subclass ParentView:
class ViewOne : ParentView {
override func hide() {
print("VIEW ONE")
}
}
class ViewTwo : ParentView {
override func hide() {
print("VIEW TWO")
}
}
So even if you later place this code:
let view = cell.viewWithTag(someTag)
// and I want to write this below without casting
view.hide()
you won't need to explicitly cast your UIView's, the view will call it's intended overridden method, unless and until you call super in your overridden method also
EDIT: More on making use of protocols
In the case you need other controls to also have a hide() method to override then you can still have to subclass, for example in the case of UILabel you need to override it:
class ParentLabel : UILabel, SomeProtocol {
func hide() {
print("PARENT LABEL")
}
}
then you can write the intended code with casting to your protocol
if let view = cell.viewWithTag(someTag) as? SomeProtocol {
view.hide() // prints PARENT LABEL
}
and either use that subclassed UILabel control or if you need in some cases some label to override that behavior then you can still create a child subclass of ParentLabel:
class LabelOne : ParentLabel {
override func hide() {
print("LABEL ONE")
}
}

Swift. Use of 'self' in method call before all stored properties are initialized [duplicate]

This question already has answers here:
Calling instance method during initialization in Swift
(8 answers)
Closed 6 years ago.
I have a class
class ChartView: UIView
{
class: DotView {
let circleView1: UIView
let circleView2: UIView
init (view: UIView)
{
self.view = view
self.circleView1 = self.buildCircle(some rect here)
self.circleView2 = self.buildCircle(some rect here)
func buildCircle(rect: CGRect) -> UIView
{
let dotView = UIView(frame: rect)
dotView.backgroundColor = UIColor.whiteColor()
dotView.layer.cornerRadius = dotView.bounds.width / 2
self.view.addSubview(dotView)
return dotView
}
}
}
But I got this error:
Use of 'self' in method call 'buildCircle' before all stored properties are initialized
So I just want to create objects in some method and then assign it to the stored properties. How can I fix my code?
You can't call methods on self before all non-optional instance variables are initialized.
There are several ways to go around that.
Change properties to optionals or implicitly unwrapped optionals
(not recommended)
Make the buildCircle() method static or just a
function in the file and call the addSubview() for all the circles
after all of the properties were initialized and you called
super.init()
etc. You just have to avoid calls to self before the
class was initialized.
For solving of this issue it is possible to use these approaches:
class MyClass: NSObject {
var prop: String = ""
override init() {
super.init()
self.setupMyProperty()
}
func setupMyProperty() {
prop = "testValue"
}
}
class MyClass1: NSObject {
var prop: String = ""
override init() {
prop = MyClass1.setupMyProperty()
super.init()
}
class func setupMyProperty() -> String{
return "testValue"
}
}
You can create your circleView in the function willMoveToSuperview.
In this lifecycle function you are sure to have ChartView allocated properly, so:
override func willMoveToSuperview(newSuperview: UIView?) {
self.circleView1 = self.buildCircle(some rect here)
self.circleView2 = self.buildCircle(some rect here)
}

How to make an array of objects that confirm a certain protocol and have a different parent class?

I have a design related question.
There is a class inherited from CAShapeLayer and one another class inherited from CATextLayer and both of them confirm to a certain protocol like below.
protocol HogeProtocol {
func aFunction()
}
class A: CAShapeLayer, HogeProtocol {
func aFunction() {
print("I am Class A")
}
}
class B: CATextLayer, HogeProtocol {
func aFunction() {
print("I am Class B")
}
}
A subclass of UIView has an array of objects that confirm this protocol:
class CustomView: UIView {
var customLayers = [HogeProtocol]()
func callTheFunctionAndAddSublayer() {
// some implementation
}
}
What I am trying to do here is to call aFunction() of the customLayers and add them to them to the layer of this custom UIView.
class CustomView: UIView {
var customLayers = [HogeProtocol]()
func callTheFunctionAndAddSublayer() {
for customLayer in customLayers {
customLayer.aFunction() // can call
layer.addSublayer(customLayer) // cannot call..
}
}
}
In this case, the elements is confirming protocol but cannot be added to sublayers because they are not inherited from CALayer. I wish I could make an array of an object inherited from CALayer (which is the common parent class for CAShapeLayer and CATextLayer) AND confirming to the protocol but swift doesn't allow me to do so (as far as I know...)
It seems very a simple problem and guess there might be a solution already but I couldn't find any answers after hours of google researching...
Any ideas?
You'll need to downcast the array object with as? or as! to a CALayer.
Try this:
for customLayer in customLayers {
customLayer.aFunction()
layer.addSublayer(customLayer as! CALayer) // would crash if customLayer is not of CALayer
}
You can cast the object to CALayer and check if it is really a kind of CALayer.
class CustomView: UIView
{
var customLayers = [CALayer]()
func callTheFunctionAndAddSublayer()
{
for customLayer in customLayers
{
customLayer.aFunction() // can call
if let layer = customLayer as? CALayer where layer.isKindOfClass(CALayer)
{
layer.addSublayer(layer)
}
}
}
}

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