I have a UIBarButtonItem subclass, "NavBarButtonItem", defined in Swift 3.0 but am not sure if I've set up the convenience initializer properly. This initializer seems works fine in other Swift classes:
let nextBarButtonItem = NavBarButtonItem.init(self, "Next", #selector(pressedNextButton))
However, in Obj-C, Xcode presents me with the following (it's missing argument labels):
self.nextButton = [NavBarButtonItem alloc] init:<#(id<NavBarButtonDelegate> _Nonnull)#> :<#(NSString * _Nonnull)#> :<#(SEL _Nonnull)#>
and I've completed it with:
self.nextButton = [[NavBarButtonItem alloc] init:rvc :2 :#"Next" :#selector(doneButtonTapped:)];
I've not set up initializers in Swift before so suspect that there might be something wrong but the app compiles. Here's the Swift subclass:
MyViewController.swift
// this file contains other classes that precede subclass NavBarButtonItem
..
#objc protocol NavBarButtonDelegate {
func pressedNextButton()
}
#objc class NavBarButtonItem: UIBarButtonItem
{
private var delegate: NavBarButtonDelegate?
init(_ delegate: NavBarButtonDelegate){
self.delegate = delegate
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
convenience init(_ delegate: NavBarButtonDelegate, _ title: String, _ selector: Selector)
{
self.init(delegate, wtfProperty)
self.title = title
self.action = selector
}
}
Related
I am trying to pass value from a swift class to an objective C class, but get an error. The error is
"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[
MainViewController childViewControllerResponseWithAsset:]: unrecognized selector sent to instance 0x7f969a133c00"
ChildViewController swift class:
#objc protocol ChildViewControllerDelegate
{
func childViewControllerResponse(asset:AVAsset)
}
class ChildViewController:UIViewController
{
#objc var delegate: ChildViewControllerDelegate?
#objc var asset:AVAsset!
#objc func apply() {
self.delegate?.childViewControllerResponse(asset: self.Video())
//dismiss view
self.navigationController?.popViewController(animated: false)
}
}
MainViewController objective C class:
#import "Project-Swift.h"
#interface MainViewController()<ChildViewControllerDelegate>
{
-(IBAction)ButtonPressed:(UIButton *)sender{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ChildViewController *vc = (ChildViewController*)[storyboard instantiateViewControllerWithIdentifier:#"ChildViewController"];
AVAsset *asset = self.originalVideoAsset;
vc.asset = asset;
vc.delegate = self;
[self.navigationController pushViewController:vc animated:YES];
}
// Define Delegate Method
-(void)childViewControllerResponse:(AVAsset*)asset
{
self.originalVideoAsset = asset;
}
}
How I will solve this problem or what did I wrong?
The Swift method childViewControllerResponse turns into the Objective-C method childViewControllerResponseWithAsset. That is just how Swift-to-ObjC conversion works. Therefore, you should rename your Objective-C method to:
-(void)childViewControllerResponseWithAsset:(AVAsset*)asset
{
self.originalVideoAsset = asset;
}
Alternatively, you can apply a #objc attribute to the Swift method, and specify the name you want:
#objc(childViewControllerResponse)
func childViewControllerResponse(asset:AVAsset)
I was using Xcode 10.1 and used SWIFT_VERSION 3.0 in Objective-C project.
The below code worked fine with Swift 3.
RichTextEditor.swift:
init(frame: CGRect, editAccess: Int) {
super.init(frame:frame)
}
Objective-C class calls above method:
self.rchTextControl = [[RichTextEditor alloc] initWithFrame:CGRectMake(2, cellRowHeight, tableView.tableView.bounds.size.width-4, cellMessageHeight-cellRowHeight) editAccess:[sectionCtrlEditAccessValue intValue]];
Above Objective-C code was working with Swift 3 and once I changed it to SWIFT_VERSION 4, the below error occurred.
How can I fix this issue? I was googling this but I could not find a solution.
You need to add #objc in method declaration to get access of it in objective c.
like this,
class RichTextEditor : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
#objc init(frame: CGRect, editAccess: Int) {
super.init(frame:frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
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.
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.
Here is an UIView extension written in ObjectiveC to easily create view for using Auto-layout:
+(id)autolayoutView
{
UIView *view = [self new];
view.translatesAutoresizingMaskIntoConstraints = NO;
return view;
}
it invokes [self new] so any subclass of UIView can use this method. How can I achieve this in Swift?
OK, this appears to be the solution. The type must have a required initializer with the correct parameter list (in this case no parameters).
class SubView: UIView {
override required init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
class func autolayoutView() -> UIView {
var view = self()
view.setTranslatesAutoresizingMaskIntoConstraints(false)
return view
}
}
Though both Carrl and Gregory Higley are right with there solutions, including thr remark about the fact that self() needs to use a required init i wanted to post a more general example:
class Human {
var gender = String()
required init() {
self.gender = "any"
}
class func newHuman() -> Human {
return self()
}
}
class Man : Human {
required init() {
super.init()
self.gender = "male"
}
}
var someMan = Man.newHuman()
println(someMan.gender) // male
Inspired by Gregory Higley, i think the solution is here:
extension UIView{
class func autolayoutView() -> UIView {
var view = self()
view.setTranslatesAutoresizingMaskIntoConstraints(false)
return view
}
}
Update for Swift2.1:
extension UIView{
class func autolayoutView() -> UIView {
let view = self.init()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}
}