iOS Swift - Accessing method in Child from Parent - ios

In my app I have a Storyboard with a bunch of elements laid out. I am setting properties of these elements from "ViewController.swift".
On the storyboard are two UIViews, which have been subclassed to allow for drawing methods. They are to be used as "signature" fields, where a user can sign their signature into the box. This is working fine.
One of the subclassed methohds of the UIViews is "eraseBitmap" which clears the UIView of the drawing.
class SigView: UIView {
...
func eraseBitmap() {
path = nil
cache = nil
}
}
I have a button that calls a function "erase" in the parent VC.
I have tried doing
func erase() {
SigView.eraseBitmap()
}
However that generates an error saying that I'm missing an argument. eraseBitmap, however, accepts no arguments.
If I add an argument, regardless what it is, I get a "SigView not convertible to..." error.
Any ideas, or a better way of coding this part?

Your SigView class defines a method eraseBitmap() - something like:
class SigView : UIView {
func eraseBitmap () { /* ... */ }
}
You'll apply this method to instances of SigView. So, somewhere you've got an instance of SigView in your UI, like:
var aView : SigView = ...
You'll then use:
func erase () {
aView.eraseBitmap()
}
or perhaps
func erase (view:SigView) {
view.eraseBitmap()
}
The error you are getting is caused by attempting to invoke a non-class method on a class. Non-class methods can only be invoked on instances of classes.

Related

Elegant way to pass a "partial closure"?

In my ViewController, I am setting up multiple so-called PanelControls.
These PanelControls are initialized with a title, UISlider and information, what property of another UIView called ViewToEdit to change with that slider (ControlPanel has a reference to it).
It is always one single property of type CGFloat or UIColor.
I want to be able to pass a property (not a value) of ViewToEdit when initializing a PanelControl.
So I wanna use it like this:
PanelControl(title: "doesnt matter", propertyToEdit: ViewToEdit.propertyToEdit)
And PanelControl would implement it like this:
class PanelControl: UIView {
...
func sliderChanged(slider: UISlider) {
propertyToEdit = slider.value
}
}
please not that the above code is just my fantasy and doesn't actually work. It just illustrates my desired usage.
This way I could create many instances of PanelControl and pass each one different information on which property of ViewToEdit they control.
I have tried:
Using a closure, but that does not fit because it is not a complete statement I want to pass. Rather a part of a statement. So viewToEdit.propertyToEdit = ... with the right side of that statement set by PanelControl when executing it.
Literally passing it ViewToEdit.propertyToEdit but that obviously makes no sense as well.
What to I do?
How about using key paths:
class PanelControl: UIView {
let changer: (Float) -> Void
init<T: AnyObject>(title: String, object: T, property: ReferenceWritableKeyPath<T, Float>) {
changer = { object[keyPath: property] = $0 }
}
func sliderChanged(slider: UISlider) {
changer(slider.value)
}
}
You can use it like this:
PanelControl(title: "doesnt matter", object: myViewModel, property: \ViewToEdit.propertyToEdit)

How to call the same function on multiple classes?

I have an UIViewController with 4 UIButtons. A user can tap any of those UIButtons and an UIView pops up. I want to add an didAppear() and didDisappear() function on the classes which are holding the UIViews depending on the users action. How can I call didDisappear() without the use of an enum, for example:
func didDisappear(view: EnumViews){
switch view{
case view0: myClassWithView0.didDisappear()
case view1: myClassWithView1.didDisappear()
case view2: myClassWithView2.didDisappear()
case view3: myClassWithView3.didDisappear()
}
}
Now I get 4 times duplicate data. I know that function exists for my class with a UIView, but how to call it? I made a protocol:
protocol ViewProtocol{
func didAppear()
func didDisappear()
}
I made the classes which are holding the UIView's conform to that protocol. However I do not know how to use it, when I create the class I get the error:
'myClassWithUIView' cannot be constructed because it has no accessible
initializers
The classes are all in an array and I can identify which UIView needs to pop up from the sender.tag. Ideally, I want to have something like this:
#IBAction func bringNewUIView(_ sender: UIButton) {
let newView = myArrayOfClassesWithUIView[sender.tag]
newView.didAppear()
}
You've got many things going on here. I'll start with the easy one.
'myClassWithUIView' cannot be constructed because it has no accessible initializers
This just means you don't have an initializer for your class. So inside your myClassWithUIView implementation you need to have init. I can't really help you with building the init because I don't know how that class is structured, but I will assume this is something you know how to do anyway.
Your #IBAction seems fine. Once you have an array of your classes that seems like it should work. Edit your post if that is not the case.
Finally, for your didDisappear question, you can do something like this:
func didDisappear(view: EnumViews) {
//Check to see if this view conforms to your ViewProtocol (that's not a good name, btw)
if let myClass = view as? ViewProtocol {
//Since it does conform to ViewProtocol you can call didDisappear on it
myClass.didDisappear()
}
}
Alternatively, if you already know that the didDisappear function is always passing in a view that conforms to ViewProtocol why not just change the argument and make that easier?
func didDisappear(view: ViewProtocol) {
view.didDisappear()
}

Protocol Oriented Programming, implicitly calling extension method

Having my first crack at POP. In this case I want to decorate some UIViewControllers so that any that they automatically raise a 'Page viewed' analytics event.
So I created a protocol, and and extension for that protocol:
protocol ReportPageViewedEvent {
func reportPageViewed()
var pageName : String? { get set }
}
extension ReportPageViewedEvent where Self: UIViewController
{
func reportPageViewed()
{
guard let pageName = self.pageName else
{
fatalError("UIViewController implements ReportPageViewEvent protocol but did not set pageName property")
}
let eventBusiness = EventBusiness.sharedInstance
eventBusiness.logUserViewedPage(pageName)
}
}
This works as I want, if I decorate a UIViewController with ReportPageViewedEvent like this:
class HomeView: UIViewController, ReportPageViewedEvent {
I get a compiler error unless I set 'pageName' which is exactly what I want.
Where I am getting unstuck is where and how to call the actual reportPageViewed() method. I really want it to be called from viewDidLoad which means I either have to modify every 'viewDidLoad' in every controller that uses it, or subclass and call the method in the super class which defies the point of using POP in the first place.
Is there a nice way to achieve this. I can't find an example like this in any tutorial/blog.
Basically, there is always some behaviour shared by all the screens of your app. So it is appropriate to create a class called (for example) BaseViewController so all the other view controllers will inherit from it.
In BaseViewController's viewDidLoad you can call the reportPageViewed() method.
However, this approach makes the Protocol Oriented Programming not needed. Protocols are useful when you need to assign some same behaviour to objects that have nothing in common (which is not the case for app screens).

Assigning UITableView delegate and dataSource to super in Swift

I am trying to have a super class fill and handle the contents of a table for a certain segment. So I thought of implementing:
#IBAction func changeValue(sender:AnyObject){
self.searchDisplayController?.setActive(false, animated:false)
if (selection.selectedSegmentIndex==1){
self.myTableView.delegate=super
self.myTableView.dataSource=super
} else {
self.myTableView.delegate=self
self.myTableView.dataSource=self
}
self.myTableView.reloadData()
}
Yet I have an error. By way of testing, the compiler suggested me to use:
#IBAction func changeValue(sender:AnyObject){
self.searchDisplayController?.setActive(false, animated:false)
if (selection.selectedSegmentIndex==1){
self.myTableView.delegate=super.self()
self.myTableView.dataSource=super.self()
} else {
self.myTableView.delegate=self
self.myTableView.dataSource=self
}
self.myTableView.reloadData()
}
whatever the meaning of construct super.self()
Yet, notwithstanding the code passes through there without any problem, the command seems to be ignored and the delegate methods are called on the same class instead of the super one and even printing the value of super.self() shows it is the current class, notwitstanding, when I skip the (), Xcode encourges me by saying:
Function produces expected type 'LogsViewController'; did you mean to call it with '()'?
Yet when I add the double parenthesis it returns the current class instead of the top LogsViewController.
What is the correct way of implementing what I need, and why super.self() does not work as it is advertised?
I clarify I cannot simply call the delegate methods on super as that shall retain the ownership to the bottom class and virtual methods on a top class shall find the methods in the bottom class instead of the one of the super one as it would be needed.
That its the structure of my code, hoping it makes things clearer:
Bottom Class
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
let isMainTable = tableView==self.myTableView
let isFavorite = selection.selectedSegmentIndex==2
var count: Int?
count = sourceForTableKind(isMainTable, favorites:isFavorite)?.count
if (count==nil) {
return 0
}
return count!
}
Top "virtual class":
func sourceArray()->Array<NearElement>?{
return nil
}
func arrayOfContentsFromSearchArray(searchArray:Array<String>?, favorites:Bool)->Array<NearElement>?{
return nil
}
func sourceForTableKind(normal: Bool, favorites:Bool)->Array<NearElement>?{
// NSLog(#"source:%# %#",favorites?#"favorites": #"near", normal?#"normal":#"searched");
print("sono \(self)")
if (normal) {
return sourceArray()
} else {
return arrayOfContentsFromSearchArray(searchResults, favorites:favorites)
}
}
The implementations of sourceArray() and arrayOfContentsFromSearchArray() are defined both in the bottom class and its super class both inheriting from this class.
Simply because when you are assigning delegate you are assigning instances.
So if the delegate method is called it will call the object's method which is an instance of "self" class.
When you assign it to super it keeps referencing to your instance and it doesn't matter if you used "self" or "super"(this is to confirm).
What you should do, in your class implementation of the delegate method call the super's implementation.
you have to put:
myTableView.delegate = self
myTableView.dataSource = self
When you assign it to super it keeps referencing to your object and it doesn't matter if you used "self" or "super".
A solution for this case would be:
Superclass implements tableview's delegate and datasource methods.
A separate class (called for example TableDelegate) which implements tableview's delegate and datasource methods.
if (selection.selectedSegmentIndex == 1) {
self.myTableView.delegate = self;
self.myTableView.dataSource = self;
} else {
self.myTableView.delegate = instanceOfTableDelegate;
self.myTableView.dataSource = instanceOfTableDelegate;
}`
When superclass delegate, datasource are needed to be called, assign self, Swift will find the implementation from the superclass, otherwise assign to the class which is used only as delegate and datasource for the tableview.
I ended up, as also suggested, by calling the super on all delegate methods. Of course it would have been cleaner to switch the table or its delegate and dataSource to the top class, but that simply does not work in Swift.
Yet, even this pushes the virtual function to call the bottom implementation. Calling the top delegate methods does not apparently change the active instance the virtual function finds when implementing its methods. Of course I would need to awkwardly send upwards even the implementations, but this is definitely a bug of the support.

Pass a Class (UILabel) with its properties through a function Swift

I am wondering if anyone knows how to (if possible) pass a UILabel through a function while being able to access and change its properties? Here's what I have:
func plusMinusChange(minus: UILabel, plus: UILabel) {
if (minus.hidden) {
minus.hidden=false
plus.hidden=true
} else {
minus.hidden=true
plus.hidden=false
}
}
And here's how I am calling it:
plusMinusChange(firstMinus, firstPlus)
I know this is probably really illogical but I want to give it a try anyways. If you were wondering, firstMinus and firstPlus are linked to UILabels on the storyboard.
Calls to methods (that is, funcs defined within a class or other type) require parameter labels for the second (and subsequent) parameter but not the first. If you want to change which labels are required at the call site, you change the declaration.
To require a label on the first parameter:
func plusMinusChange(#minus: UILabel, plus: UILabel) {
To require no label on the second:
func plusMinusChange(minus: UILabel, _ plus: UILabel) {
There is no need to use a conditional if there. You can use ! in front of the property to toggle its value as follow:
minus.hidden = !minus.hidden
plus.hidden = !plus.hidden

Resources