Adding convenience initializers in extensions and invoking them on subclasses - cocoa-touch

I’m trying to add an extension method to UIView in Swift like so:
extension UIView {
convenience init(superview: UIView) {
self.init(frame: CGRect())
superview.addSubview(self)
}
}
Later, I can use this new initializer, but only on UIView and I can’t figure out how to use this on my own subclasses.
var poView : UIView = UIView(superview: someSuperview) // Just dandy
var myViewSubclass : MyViewSubclass(superview: someSuperview) // No good.
Now, my view subclass implements both init(frame: CGRect) and init(coder: NSCoder) and currently, nothing else.
Why can’t I use my convenience initializer?

In order to inherit added convenience initialiser automatically, your MyViewSubclass must also override plain init(), which is inherited from NSObject.
class MyViewSubclass {
override init() {
super.init()
}
...
}

Related

Can't call custom swift init from objective-c class

I have a custom class with a custom init written in swift. I need to call that init from an Objective-C class.
Swift
#objc public class MyClass: NSObject {
public init(configuration config: Data)
{
super.init()
// Do Stuff
}
}
Objective-C
[[MyClass alloc] initWithConfiguration:CONFIG];
But when I call the init from Objective-C the compiler complains that
No visible #interface for 'MyClass' declares the selector
'initWithConfiguration:'
What am I missing here?
You have to add #objc attribute to initializers too. Like this:
#objc public init(configuration config: Data)
{
super.init()
// Do Stuff
}
And after this, don't forget to re-build (CMD+B), otherwise Xcode will stupidly emit an error.

Dynamic subclass instantiation in swift

I've come to Swift from Objective-C and there's a lot of things that Objective-C can do but swift is much more complicated. Such as OOP dynamic initializer.
E.g. I've got this code working in Objective-C:
#interface CommonVC: UIViewController
+ (instancetype)showFrom:(UIViewController *)vc;
#end
#implementation CommonVC
+ (instancetype)showFrom:(UIViewController *)vc {
CommonVC *instance = [self instantiateFrom:vc.nibBundle];
[vc presentViewController:instance animated:YES completion:nil];
return instance;
}
// this is like convenience initializer.
+ (instancetype)instantiateFrom:(NSBundle *)aBundle {
return [self.alloc initWithNibName:NSStringFromClass(self.class) bundle:aBundle];
}
#end
#interface SubClassVC: CommonVC
#end
And then use the subclass or superclass like this:
SubClassVC *subVC = [SubClassVC showFrom:self];
// or in swift:
SubClassVC.show(from: self)
However, in swift, it seems impossible to implement something like that. I've tried a few, but always got compile error. Here's one:
class CommonVC: UIViewController {
class func show(from sender: UIViewController) -> Self {
let vc = self(sender: sender) // Compiler error: Constructing an object of class type 'Self' with a metatype value must use a 'required' initializer
sender.present(vc, animated: true, completion: nil)
return unsafeDowncast(vc, to: self)
}
convenience init(sender: UIViewController) {
self.init(nibName: type(of: self).className, bundle: sender.nibBundle)
loadView()
}
}
So how do I write generic convenience initializer of a viewController from the super class and then call that with the subclass?
Of course, my convenience init has lots of stuff that I just cut down to this simple code, also the function show(from:) has a different presentation instead of this simple present(_:animated:completion:).
Even if I make a function to do the setup after initialize, it still wouldn't work
class CommonVC: UIViewController {
class func show(from sender: UIViewController) -> Self {
let vc = self.init(nibName: type(of: self).className, bundle: sender.nibBundle) // Compiler error: Constructing an object of class type 'Self' with a metatype value must use a 'required' initializer
vc.setupAfterInitialize()
sender.present(vc, animated: true, completion: nil)
return unsafeDowncast(vc, to: self)
}
convenience init(sender: UIViewController) {
self.init(nibName: type(of: self).className, bundle: sender.nibBundle)
setupAfterInitialize()
}
internal func setupAfterInitialize() {
// do stuff
loadView()
}
}
And the code looks stupid, doesn't make convenience init convenience.
For now, I can't use the class function show(from:) but have move the presentation outside and make things like:
CommonVC.show(from: self)
SubClassVC(sender: self).present()
// instead of this simple presentation:
// SubClassVC.show(from: self)
I've even tried this but still not working:
class func show<T: CommonVC>(from sender: UIViewController) -> Self {
T.init(nibName: type(of: self).className, bundle: sender.nibBundle)
....
When you switch from Objective-C to Swift, it's tempting to simply translate your Objective-C style into Swift code. But Swift is fundamentally different in some ways.
It may be possible to implement a generic class which all your controllers are subclass of, but we tend to try and avoid inheritance in Swift where possible (in favour of protocols and extensions).
A good Swift rule of thumb, from Apple, is: "always start with a protocol"...
It's actually very easy to implement what you want using a protocol, and extension:
protocol Showable {
init(className: String, bundle: Bundle?)
static func show(from: UIViewController) -> Self
}
extension Showable where Self: UIViewController {
init(className: String, bundle: Bundle?) {
self.init(nibName: className, bundle: bundle)
}
static func show(from: UIViewController) -> Self {
let nibName = String(describing: self)
let instance = self.init(className: nibName, bundle: from.nibBundle)
from.present(instance, animated: true, completion: nil)
return instance
}
}
In the above code, I've declared a Showable protocol and an extension that provides a default implementation that applies where the adopting class is an instance of UIViewController.
Finally, to provide this functionality to every single view controller in your project, simply declare an empty extension:
extension UIViewController: Showable { }
With these two short snippets of code added you can now do what you describe in your question (as long as an appropriately named nib exists for your view controller instance):
let secondController = SecondViewController.show(from: self)
ThirdController.show(from: secondController)
And that's the beauty of Swift. All your UIViewController subclasses now get this functionality for free; no inheritance required.

Swift subclass inheriting Objective-C class does not show all superclass methods

This entire question premise might be completely ridden with issues due to the fact that I am updating a project written in Objective-C with Swift.
First off, I am trying to access methods belonging to a class written in Objective-C from a Swift subclass that has inherited it. However there are some methods that I can see and access but others are invisible (act as if they do not exist).
The .h file for the Objective-C class is as follows:
#import <UIKit/UIKit.h>
#interface BaseMenuViewController : UIViewController
-(void)applyColourForeground:(UIColor *)foreground background:(UIColor *)background;
-(void)colorButton:(UIButton *)button foreground:(UIColor *)foreground background:(UIColor *)background;
- (void)spreadElementsVertical:(UIView *)firstArg, ...;
- (void)spreadElementsVerticalWithSeparation:(CGFloat)separation elements:(UIView *)firstArg, ...;
#end
The Swift class looks like this:
import UIKit
class FontViewController: BaseMenuViewController {
var BackButton: UIButton
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
The first two methods applyColourForeground and colorButton are both visible and callable from my Swift subclass but the other two spreadElementsVertical and spreadElementsVerticalWithSeparation are not. I am attempting to access them via super.<function> and BaseMenuViewController.<function> where <function> is the name of each method but the IDE only shows the aforementioned two.
What am I missing?
Any help is appreciated.

What is equivalent of id<type> in Swift?

I have two delegates like this:
protocol MyFirstDelegate {
func change(value: int)
}
protocol MySecondDelegate {
weak var delegate: MyFirstDelegate?
}
Those protocols are implemented in two customized UIViewControllers:
class MyFirstViewController: UIViewController, MyFirstDelegate {
...
}
class MySecondViewController: UIViewController, MySecondDelegate {
...
}
The first UI view controller will push second UI view controller by using a segue.
In my first UI view controller class I have the following codes to prepare segue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if let vc = (segue.destinationViewController) as? MySecondViewController {
vc.delegate = self
}
}
The above codes compile without any problem. However, I think that the first UI controller has knowledge about the next view controller class. I prefer to pass delegate like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if var vc = (segue.destinationViewController) as? MySecondDelegate {
vc.delegate = self
}
}
I got compiling error:
Cannot assign to property: "vc" is immutable.
It seems that as to a concrete UI view controller is OK, but not to a protocol. Any explanation and solution to this?
By the way, in Objective-C, this can be done like this:
id<NSObject, MySecondDelegate> vc = segue.destinationViewController;
vc.delegate = self;
I could not find a way in swift to define a variable like
let vc: AnyObject<type>? // like id<type>
UPDATES
As #David_Berry solution, it works if I mark protocol as class. The above codes are actually simplified ones. I am in the middle of converting my ObjC codes to Swift. Therefore, I have to mark protocols as objc:
#objc protocol MyFirstDelegate : class {
func change(value: int)
}
#objc protocol MySecondDelegate {
weak var delegate: MyFirstDelegate?
}
with #objc, I still get the same compiling error.
Well, I finally managed to get your example to compile, you have a LOT of inconsistencies. The key to your actual problem, I think, is requiring that the protocols be class protocols:
protocol XXX : class { }
Says that only types that are classes can be declared to implement XXX.
But, I'm really not sure that's your problem, there were so many other changes required (you spelled MySecondDelegate at least two ways, MySecondViewController at least two ways, etc.
The compiling example is:
// This protocol has to be class so that delegate can be weak
protocol MyFirstDelegate : class {
func change(value: Int)
}
protocol MySecondDelegate {
weak var delegate: MyFirstDelegate? { get set }
}
class MyFirstViewController: UIViewController, MyFirstDelegate {
func change(value: Int) {
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if var vc = (segue.destinationViewController) as? MySecondDelegate {
vc.delegate = self
}
}
}
class MySecondViewController: UIViewController, MySecondDelegate {
weak var delegate : MyFirstDelegate?
}
As for your question, there is no swift synonym to the objective-C syntax id<Protocol> or Class<Protocol> There are multiple duplicates to that issue that offer alternatives.

Swift function overriding Objective-C method

I have an Objective-C method (declared in the class named "BaseViewModel" of my old framework) that is :
-(void) updateFromManagedObject:(__kindof NSManagedObject *)entity;
I want to use it in mutiple Swift classes. Each Swift class will use a particular subclass of NSManagedObject and inherits from "BaseViewModel". When i try to override this func like this :
override func updateFromManagedObject(entity: Person?) {
<#code#>
}
OR
override func updateFromManagedObject(entity: Animal?) {
<#code#>
}
the compiler returns :
Method does not override any method from its superclass
It only works with :
override func updateFromManagedObject(entity: NSManagedObject?) {
<#code#>
}
How can I use specifics inherited types of NSManagedObject ? (Maybe with a class Generic-Type ? I try but failed too :/ )
The point of overriding is that the subclass method is called instead of the superclass method when the receiver is an instance of the subclass. Therefore, the subclass method's parameters must handle at least all the parameters the superclass method can handle. So the subclass method's parameters' types must be the same or more general than the parameters' types for the superclass method it overrides.
You cannot overload Objective-C functions based on the argument type, as Objective-C doesn't support this kind of overloading. This is why only the NSManagedObject overriding works.
If you want different behaviour based on the entity type, an alternative would be to declare a Swift-only method:
func updateFromManagedObject2(animal: Animal) {
}
func updateFromManagedObject2(person: Person) {
}
You can't override that method because types in objective-c and swift are a bit different. the types have to line up exactly, or overriding won't work.
Both statements So the subclass method's parameters' types must be the same or more general than the parameters' types for the superclass method it overrides. and You cannot overload Objective-C functions based on the argument type, as Objective-C doesn't support this kind of overloading above are correct.
Another example of overriding illustrating this:
- (UIColor*)getDataPointColor:(int)index
{
return UIColor.whiteColor;
}
override func getDataPointColor(_ index: Int32) -> UIColor {
return UIColor.redColor
}
Note that override func getDataPointColor(_ index: Int) -> UIColor { won't work. (Not sure why, 64-bit code and all...)