Swift Protocol - Property type subclass - ios

I'm defining a protocol called PanelController in which I'd like to store a PanelView. PanelView itself is a subclass of UIView and defines the basic structure of panel. I have three different views that subclass PanelView: LeftPanel, MidPanel, and RightPanel. For each of those panels I'd like to define a xxxPanelController (left, mid, right) that conforms to the PanelController protocol.
The issue I'm running up against is in the protocol and xxxPanelController
protocol PanelController {
var panelView: PanelView { get set }
...
}
and
class LeftPanelController: UIViewController, PanelController {
var panelView = LeftPanelView()
...
}
where
class LeftPanelView: PanelView {
...
}
and (one last piece...)
class PanelView: UIView {
...
}
I get an error saying that: LeftPanelController does not conform to protocol PanelController for an obvious reason: panelView is of type LeftPanelView not PanelView. This seems really limited to me, though, because LeftPanelView is a subclass of PanelView so it should just work! But it doesn't!
Can someone explain to me why this is, and if anyone can come up with one, a possible workaround? Thanks!

The problem is with the setter in the protocol.
Let's say you want to GET the panelView from LeftPanelController. That's fine, because LeftPanelView can do everything PanelView can do (and more).
If you want to SET the panelView of LeftPanelController though, you can give it any PanelView. Because you're defining the panelView variable as a LeftPanelView, the setter could sometimes fail.
To fix this, you could do the following in LeftPanelController:
var panelView: PanelView = LeftPanelView()
The implication of this is that you won't be able to access any methods or properties that are specific to LeftPanelView without casting it first. If that's not an issue, then this should fix your problem!

Related

How to use foreign variables in classes

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.

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).

"Unknown class in interface builder file" when using class which inherits from a generic class in a Storyboard

I recently refactored my class BookTableViewController from a simple inheritance from UITableViewController, so that it now inherits from a generic class FetchedResultsTableViewController<TResultType, TCellType> which itself inherits from UITableViewController.
The class declarations look like this:
class BookTableViewController: FetchedResultsTableViewController<Book, BookTableViewCell> {
override func viewDidLoad() {
// breakpoints in here do not catch!
}
}
class FetchedResultsTableViewController<TResultType, TCellType: UITableViewCell>:
UITableViewController, NSFetchedResultsControllerDelegate {
// implementation here
}
In the Storyboard, the Custom class and Module are both set, and I can click the arrow to jump to the code for the BookTableViewController class, suggesting that the storyboard is linked correctly to the class. However, when I try to run the application, the class is not recognised - code in viewDidLoad() does not run, and I receive the following logged message when running my app:
Unknown class _TtC12Reading_List23BookTableViewController in Interface Builder file.
I am running XCode 7.3 (Swift 2.2). Is this a limitation with Storyboards, a bug, or have I missed something?
Thanks!
UPDATE:
After some experimentation, it does seem to be related to the generic inheritance, rather than the accessibility of the class. With the following classes defined:
import Foundation
import UIKit
// Both of these classes are accessible by the Storyboard
class FirstInheritance : UITableViewController{}
class SecondInheritance : FirstInheritance{}
// The generic class is also accessible
class GenericViewController<T> : UITableViewController{}
// But this class is not accessible...
class ConcreteViewController : GenericViewController<String>{}
all classes except the ConcreteViewController are suggested in the Storyboard's class autocomplete (although the generic class also does not work, as there is no way to specify the type arguments in the Storyboard).
Little late to the game, but this info might come in handy for anyone bumping into the thread.
Actually, as far as I can tell, there is now, as of Swift 3, some kind of awkward support for generics in storyboards.
I've managed to write a generic base class extending UIViewController, then constraining several subclasses of that generic class and using them in the storyboard.
The actual code has a similar layout to what is specified bellow:
class GenericParent: UIViewController {
// Has a few, non-generic members ...
}
class ActualGenericClass<T>: GenericParent {
// There are more generic members in the actual class
var adapter: Adapter<T> = Adapter()
}
// This class is usable in Interface Builder;
// although auto-complete won't show it, fully writing down
// the class name works
class SpecificInstanceOfActualGenericClass: ActualGenericClass<Int> {
#IBOutlet weak var tableView: UITableView!
}
That works perfectly, to my surprise!
On the other hand, the next example doesn't seem to work:
class GenericCell<T>: UITableViewCell {
var node: T? = nil
}
class SpecificCell: GenericCell<Int> {
#IBOutlet weak var coolTitleLabel: UILabel!
}
As before, explicitly writing the cell's class name on Interface Builder (on the cell's dynamic prototype) sets the class on the table view cell, and outlets are visible.
At runtime, though, when instancing the UIViewController that contains the cell's dynamic prototype, the "unknown class in interface builder file" warning is displayed and the App crashes when dequeueing the cell.
So #Andew Bennet, the statement that:
Storyboards do not support classes which inherit from generic classes
Doesn't seem to be 100% true any more, although you at least WERE right; it's the first time I've managed to pull this one off, albeit only partially!
It bugs me that some things work, others don't, but it's better than nothing.
This is so damn straightforward in Java...
Storyboards do not support classes which inherit from generic classes, either directly or indirectly. If you use a class which inherits from a generic class in a storyboard, you will get an error at runtime stating that the class is unknown.
An alternative is to use a protocol with typealiases and extensions:
protocol MyProtocol {
associatedtype genericType1
associatedtype genericType2
func myFunc(argument1: genericType1, argument2: genericType2)
}
extension MyProtocol {
func defaultFunc(argument1: genericType1){
// default implementation here
}
}
The protocol is used as follows:
class NonGenericClass: MyProtocol {
typealias genericType1 = Int
typealias genericType2 = String
func myFunc(argument1: genericType1, argument2: genericType2){
// specific implementation here
}
}
The class NonGenericClass will have all functions in the MyProtocol extension (in this case, defaultFunc(argument1: Int)).
The protocol MyProtocol can also inherit from other protocols, and the implementation of those functions can be defined in the extension to MyProtocol. Thus the protocol can make any class which conforms to it also conform to another protocol, with a standard implementation.
However, this has the limitation that you cannot specify standard overrides of class functions from within the protocol.

Swift - Same protocol for two different classes

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

Method signature return value "Class of type or subclass class"

When you have a signature like this:
- (UIView *)fooView;
You can return any subclass of UIView * (e.g UIScrollView)
And when you have:
- (Class)anyClass;
You can return any class (not an instance, the class itself) but is there a way to only allow classes of a certain class or subclass? E.g in psuedo code:
- ([UIView class])bazClass;
So here it should only be able to return a class UIView of any of its subclasses.
As specified by other users, you can't.
If your goal is to instruct other programmers about what to return from a method in your code (overriden or delegate method), you can:
write in the comment (of course...)
create a typedef like this
.
typedef Class ClassOfKindUIView;
-(ClassOfKindUIView)class
{
return [super class];
}
This won't block anything, but it can be a "talking method", an escamotage, to make the programmers stop and think "what is this??", then cmd-click and read the docs :-)
What you're looking for is a sort of type bound on the return type.
Unfortunately this is not possible in Objective-C, so I'm afraid you're out of luck.

Resources