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).
Related
I have a very general view that is created and used by multiple view controllers with 2 buttons, one of them sometimes is hidden depending on the needs.
This view delegates the tap of the two buttons.
protocol TheViewsDelegate: class {
func button1Tapped()
func button2Tapped()
}
Let's put that ViewControllerA creates this view and needs both buttons, this view controller will have to implement both delegate functions and do something inside it.
Now let's say that ViewControllerB creates the same view but just needs one of the buttons. This view controller will have to still implement button2Tapped() even though it will never be called and used.
Is there a way to handle this nicely? I imagine there's a nice solution where I don't need to implement this button2Tapped() if I don't need it.
I thought about making it optional by giving a default implementation but I don't like this solution, I like (and I think it's a good practice) the compiler giving me an error when a method it's not implement. Someone can jump into the project and not realising that he/she hasn't implement button2Tapped when needs to be implemented.
Note: This is a very simple example just to illustrate my question, but the question is more broad as in what to do when a function in a delegate is defined by controller that don't need to implement it.
I believe you want to use:
optional func
There are a couple of ways of declaring a protocol method as optional, one is using optional func which requires using #objc syntax, which a lot of programmers apparently don't like, and the other requires declaring an empty body in the extension of a protocol (which makes it optional by default).
protocol TheViewsDelegate: AnyObject {
func button1Tapped()
}
extension TheViewsDelegate {
func button2Tapped() {}
}
class SomeViewController: UIViewController, TheViewsDelegate {
func button1Tapped() {
// implement
}
}
By giving the protocol an empty body inside an extension of the protocol, that method is optional and does not need to be implemented by conforming objects.
For comparison, the alternative:
#objc protocol TheViewsDelegate: AnyObject {
func button1Tapped()
#objc optional func button2Tapped()
}
class SomeViewController: UIViewController, TheViewsDelegate {
func button1Tapped() {
// implement
}
}
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()
}
I have two .swift files so I want one of them to modify the text of an IBoutlet label that is the other class.
Class 1:
class class1: UIViewController {
#IBOutlet var label1: UILabel!
}
Class 2:
class class2: SKScene {
var cool_variable = 1
func coolFunc () {
class1.label1.text = "\(cool_variable)"
}
}
by doing this I'm getting the following error message:
Instance member "label1" cannot be used on type "class2"
Thanks in advance!
The distinction and relationship between classes and instances is absolutely crucial to object-oriented programming. Thus, consider your proposed code:
func coolFunc () {
class1.label1.text = "\(cool_variable)"
}
The problem here is that class1 is the name of a class. (That would be more obvious if you just, please, follow the elementary rule that class names start with a capital letter.) But label1 is an instance property. Thus what you need here is the name of an instance of the class1 class — presumably a reference to the actual existing instance that is already part of the view hierarchy.
You never create an instance of class1 in class2 to access the variables.
My guess is that you are using Storyboards. In this case you wouldn't want to create an instance of class1. Instead you would use delegation (This would also be a good idea if you are not using Storyboards).
Delegation can be a complicated topic, so I will try to keep this simple.
First, you start with a protocol. Usually you name it something like <CLASS-NAME>DataSource, so you would do something like:
protocol class2DataSource: class {}
The class keyword is required for delegation protocols.
Then you would add the methods to it that you want called in other classes when you call a method in the class the protocol delegates for, so, for example, willCoolFunc:
protocol class2DataSource: class {
func willCoolFunc(with variable: Int)
}
You have the parameter so you can access the variable cool_variable as you are trying to.
Next, you need to create a a variable in class2 that is of type class2DataSource:
weak var dataSource: class2DataSource?
Make sure the variable is weak and an optional.
Next, call the method, you would do it in coolFunc:
func coolFunc () {
dataSource?.willCoolFunc(with: cool_variable)
}
Now you, to access cool_variable when the function is called, you need to implement class2DataSource on class1. Create an extension of class1 that implements class2DataSource and add the function willCoolFunc:
extension class1: class2DataSource {
func willCoolFunc(with variable: Int) {
}
}
Now you can access the variable cool_variable in class1! The reason why is because when you call class2.coolFunc(), the willCoolFunc method is called with cool_variable passed in. Any class that implements the class2DataSource can access cool_variable with the willCoolFunc method.
To finish of the method, here is what the extension would look like:
extension class1: class2DataSource {
func willCoolFunc(with variable: Int) {
self.label1.text = "\(variable)"
}
}
We are almost done, but not quite. We still have to set the class1 as the data source for class2DataSource. To do this, I will reference Nikolay Mamaev from this post:
Go to the Interface Builder.
Type "Object" in the search text field of the Objects Library and drag an 'Object' to your view controller containing that connects to
class1 (i.e. do the same as you add any view or view controller to
storyboard scene, with the exception you add not a view or view
controller but an abstract object).
In the left-side 'Scenes' panel of your storyboard, highlight just added Object; in right-side panel go to the 'Identity Inspector' and
type class2DataSource instead of pre-defined NSObject. In left side
panel ('Scenes'), 'Object' will be renamed to 'Class2 Data Source'
automatically.
In the left-side 'Scenes' panel of your storyboard, control-drag from your UIView [*not the controller*] to the 'Class2 Data Source';
in pop-up appeared, select dataSource outlet.
There! You now set class1's label1's text to the value of cool_variable when you call class2.coolFunc()!
I do not know the exact problem you're trying to solve, but I'm just going to consider the part that you want to access the variable in class1 in class2. There are two basic ways to go about this, one is Inheritance and the other one is by creating an object. These are basic Object Oriented Programming concepts which you need to be familiar with.
Swift does not support multiple inheritance. So that rules out inheritance for solving you problem, since you are inheriting two classes SKScene and UIViewController.
Create an object in the class1 and call the function coolFunc
class class1: UIViewController {
#IBOutlet var label1: UILabel!
func modifyLabel(){
let someObject = class2()
someObject.coolFunc()
}
}
Of course this solution might not be the one you're looking for. You will have to explain more about the problem you're facing if you need a solution that will work for you.
In my app I've had a lot of boilerplate code I had to write whenever there was a UITableView in some controller. To eliminate it, I created a BaseTableViewController that implements some repeatedly used UITableDataSource operations like tableView(_:numberOfRowsInSection:) and tableView(_:cellForRowAt:). In tableView(_:numberOfRowsInSection:), my BaseTableViewController retrieves the actual number of rows in a section from another method (let's call it rowCount(inSection:)) and performs some computations using it, then returns the result to the delegating caller. Each class subclassing the BaseTableViewController must override the rowCount(inSection:) method and return its proper row count in a given section (BaseTableViewController itself returns 0 in its default implementation).
Now, some of my table view controllers support paging for displayed records - when the users scrolls the table view to its last row, next batch of rows is retrieved from the network. Trying to make things more protocol-oriented, I created a protocol for pageable controllers:
protocol Pageable: class {
associatedtype DataType
var pages: [[DataType]] { get set } // Each page is an array of elements, hence the table in a table
func loadNextPage()
}
If a controller is Pageable, the rowCount(inSection:) methods always looks like this:
override func rowCount(inSection: Int) -> Int {
return self.pages[section].count
}
This is tedious, because every BaseTableViewController descendant that is also Pageable must have this exact implementation, which violates DRY.
I cannot add a default implementation in a protocol extension for Pageable, because the controller already has its own implementation inherited from BaseTableViewController.
I came up with a solution, but I don't like it: I could create a PageableTableViewController (a subclass of BaseTableViewController) that provides its own overriden implementation of rowCount(inSection:), but that's not very protocol-oriented. I also tried to move the rowCount(inSection:) method to a protocol, but if I make all descendants of BaseTableViewController to conform to that protocol with a protocol extension, extending Pageable to implement the method won't work.
How would I create a mechanism where all subclasses of BaseTableViewController would be able to override the rowCount(inSection:) method, but when they're Pageable, they share a default implementation of it that's (possibly) placed in a Pageable protocol extension?
You can achieve what you are looking for with protocol extensions. the extension will apply just in case the object implementing the protocol is actually an UIViewController.
protocol something {
func method()
}
extension something where Self: UIViewController {
func method() {
//Default implementation
}
}
I want to use the same protocol for two different classes. It is for two UIStoryboardSegue classes, the normal one and the unwind segue. In my first class GameSegue.swift, I've declared this protocol
#objc protocol ViewControllerWithBackgroundImage {
var backgroundImage: UIImageView { set get }
}
I use this protocol to have access to the ViewControllers property backgroundImage. In the first class GameSegue.swift, the normal segue, the backgroundImage animates 10 px up. So in the second class GameSegueUnwind.swift, I want to do the same thing backwards, move the background 10 pxdown. But to get access to the backgroundImage property I need this protocol. Therefore it would be useful, to not declare another protocol, but instead use the same.
Any idea how this is possible?
In the second class just declare a new delegate variable
class GameSegueUnwind {
var secondDelegate: ViewControllerWithBackgroundImage?
}
and you will be able to access the function in any other class that conforms to the protocol. Of course, in the conforming class remember to declare it has the delegate handler in the prepare for segue method
destinatonViewController.secondDelegate = self