I have framework that written in Swift like this.
import Foundation
import WebKit
import ObjectiveC
public extension WKWebView {
public func someFunc(_ completionHandler:#escaping (_ capturedImage: UIImage?) -> Void) {
//Some code
}
}
When I build the frameworks and import to Objective-C code that use Cocoapods for Dependency Manager. I Can't call above someFunc function. The error said this:
No visible #interface for 'WKWebView' declares the selector 'someFunc'
This is how I implement Swift framework in Objective-C:
#import <Foundation/Foundation.h>
#import <ProjectName-umbrella.h>
#implementation CapturerDefault
- (void)captureWebViewScreenWith:(UIView *)containerView
andCompletionHandler:(void (^)(UIImage *))completion {
WKWebView *webView = [self findWebViewInViewController:containerView];
[webView someFunc: resultImage] //The error show here.
}
}
What is wrong? Did i miss something?
I tried the following steps and it works for me:
Don't import the swift file in your objc file like: #import "ExampleFile.swift" but use #import "ProjectName-Swift.h"
Make sure you use #objc statements in your swift code that you want to import to objc
Swift file:
import WebKit
extension WKWebView {
#objc public func someFunc() {
}
}
Objective C file:
#import "ViewController.h"
#import "Sample-Swift.h"
#import <WebKit/WebKit.h>
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
WKWebView *wbView = [[WKWebView alloc]init];
[wbView someFunc];
}
Credits to:
"Expected ';' after top level declarator" under Swift
Related
I'm fairly new to objective-c and swift so forgive me if this sounds dumb. Basically what I'm trying to do is *expose** a swift function to both react-native(so it can be used in JS) and to be used in objective-c. The issue I'm having is this dreaded "Duplicate interface definition for class...". I have researched and tried everything it seems but can not get rid of this error. I'm starting to wonder if is possible to do this. It seems so simple yet I just can't figure it out!
Here is my code:
AppDelegate.m
#import "MyApp-Swift.h"
#import "MyApp-Bridging-Header.h"
MyApp-Swift.h
#import "React/RCTEventEmitter.h"
#interface Counter : RCTEventEmitter
- (void)start;
- (void)stop;
#end
MyApp.swift
import Foundation
import UIKit
#objc(Counter)
class Counter: RCTEventEmitter {
#objc func start(){
}
#objc func end(){
}
}
MyApp-Bridging-Header.h
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
#interface RCT_EXTERN_MODULE(Counter, RCTEventEmitter)
RCT_EXTERN_METHOD(start);
RCT_EXTERN_METHOD(end);
#end
Inside AppDelegate.m didFinishLaunchingWithOptions() function
Counter* CounterInstance = [[Counter alloc] init];
[CounterInstance start];
If I remove the code from MyApp-Swift.h then I get the error "No visible #interface for..." BUT fixes the "Duplicate interface error" in MyApp-Bridging-Header.h. It seems like it contradicts each other!? How are you supposed to call a swift function from objective-c while also exposing the same function to JS?
I'm doing something similar in a project, and my setup looks like this:
Counter.m
#import "React/RCTBridgeModule.h"
#import "React/RCTViewManager.h"
#interface RCT_EXTERN_MODULE(Counter, NSObject)
RCT_EXTERN_METHOD(start)
RCT_EXTERN_METHOD(stop)
#end
Counter.swift
import React
#objc(Counter)
class Counter: RCTEventEmitter {
#objc func start() { /* ... */ }
#objc func stop() { /* ... */ }
}
and at the call site:
#import "YOURAPP-Swift.h" // the compiler creates this for you. don't edit!
-(void) foo {
Counter* cnt = [Counter new];
[cnt start];
}
(no other files used or modified. In particular, the bridging header isn't needed here)
I've found https://teabreak.e-spres-oh.com/swift-in-react-native-the-ultimate-guide-part-1-modules-9bb8d054db03 very helpful when I started doing this.
I need to write these two Swift functions in Objective-C and then call the returned values in Swift how do i go about doing this?
Also where do i need to put them in my AppDelegate?
Sorry this might be really easy but I have no experience using Objective-C.
Thanks in advance!
Functions to convert:
class func getAppDelegate() -> AppDelegate {
return UIApplication.shared.delegate as! AppDelegate
}
func getDocDir() -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
}
AppDelegate.m:
#import "AppDelegate.h"
#import "MainViewController.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
}
#end
AppDelegate.h:
#import <Cordova/CDVViewController.h>
#import <Cordova/CDVAppDelegate.h>
#interface AppDelegate : CDVAppDelegate {}
#end
Here is your AppDelegate class written with objective-c including your Swift method. But we need to know more detail to help.
AppDelegate.h
#import <Cordova/CDVViewController.h>
#import <Cordova/CDVAppDelegate.h>
#interface AppDelegate : CDVAppDelegate {}
+ (id)getAppDelegate;
- (NSString*)getDocDir;
#end
AppDelegate.m
#import "AppDelegate.h"
#import "MainViewController.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
return YES;
}
+ (id)getAppDelegate {
return self;
}
- (NSString*)getDocDir {
return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
}
#end
You need bridging header and thats it. Import your AppDelegate in bridging header file. The bridging header file is an interface for sharing code from objective C to Swift. So when you add any swift file to objective C project it will prompt you for adding this header file. e.g. ProjectName-Bridging-Header.h. If its not there check how to create it manually here.
Then import all needed objective C files in this header as:
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "Appdelegate.h"
Then you can add your mentioned functions in your swift file itself.
Hope it helps...
Ali's post seems to be the right answer for what you asked:
write these two Swift functions in Objective-C and then call the
returned values in Swift
But if you mean you want to call the two methods only from your Swift code, why don't you define it in an extension.
AppDelegate+.swift:
import UIKit
extension AppDelegate {
class func getAppDelegate() -> AppDelegate {
return UIApplication.shared.delegate as! AppDelegate
}
func getDocDir() -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
}
}
Of course, you need #import "AppDelegate.h" in your "...-Bridging-Header.h" .
You can use them as were in your own AppDelegate.
I'm successfully mixing and matching Obj-C and Swift in an Xcode 7 project. However, I can't seem to figure out how, in an Objective C class, to inherit from a Swift class (and yes I know about declaring that Swift class as #objc for visibility). In this case the desired Swift superclass MySwiftViewController is a subclass of UIViewController. For now, in Obj-C, I'm inheriting directly from UIViewController and not gaining access to the capabilities I added in MySwiftViewController.
Here's what i understand:
-- To declare an Obj-C class as inheriting from something, that must be in the .h file after the ':':
#import <UIKit/UIKit.h>
#interface RootViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
#end
-- To make Swift classes visible, that is #imported:
#import "MyProject-Swift.h"
However, you cannot import the Swift auto-generated bridging header into the Obj-C .h file. You also cannot forward-declare an opaque superclass with #class. So, is this possible and how?
Unfortunately, it's not possible to subclass a Swift class in Objective-C. Straight from the docs:
You cannot subclass a Swift class in Objective-C.
See Apple's guide on interoperability for more details on what you can and cannot access with Objective-C.
As for Xcode 8.0 and earlier there is dirty-hacky solution, that probably will be fixed in the future.
If you want to subclass from swift file, you can add objc_subclassing_restricted attribute. You can make it as macro for convenience.
Code:
Swift class.
import Foundation
class SwiftClass : NSObject {
func say() {
print("hi");
}
}
Objc class:
#import <Foundation/Foundation.h>
#import "test-Swift.h"
#define SWIFT_SUBCLASS __attribute__((objc_subclassing_restricted))
SWIFT_SUBCLASS
#interface ObjcClass : SwiftClass
- (instancetype)init;
#end
#implementation ObjcClass
- (void)say {
NSLog(#"oops");
}
#end
But, as I understand, it is not supported, and you may have any sort of bugs because of it. So it is not guide to action, and more like curious thing to know.
In fact, it can be achieved by category:
Swift code
import UIKit
#objc open class TestModel: NSObject {
#objc var testName: String = String()
}
Objective C .h file code
#import <Foundation/Foundation.h>
#import "Test-Swift.h"
#interface TestModel (Add)
- (void)configTestName;
#end
Objective C .m file code
#import "TestModel+Add.h"
#implementation TestModel (Add)
- (void)configTestName {
self.testName = #"12323";
}
#end
I need to create proxy pattern in iOS using swift
I have tried it using Objective C and here is the code
MyProtocol.h
#import <Foundation/Foundation.h>
#protocol MyProtocol <NSObject>
#required
-(void)testMessage;
#end
TestBO.h
#import <Foundation/Foundation.h>
#import "MyProtocol.h"
#interface TestBO : NSObject <MyProtocol>
#end
TestBO.m
#import "TestBO.h"
#implementation TestBO
-(void)testMessage{
NSLog(#"Test Message");
}
#end
TestProxyHandler.h
#import <Foundation/Foundation.h>
#interface TestProxyHandler : NSProxy
#property (nonatomic, strong) id object;
- (instancetype)initWithProtocol:(Protocol *)protocol andObject:(Class)clazz;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector;
#end
TestProxyHandler.m
#import "TestProxyHandler.h"
#import "TestBO.h"
#implementation TestProxyHandler
- (instancetype)initWithProtocol:(Protocol *)protocol andObject:(Class)clazz{
if ([clazz conformsToProtocol:#protocol(MyProtocol)]) {
self.object = [[clazz alloc] init];
}else{
NSLog(#"Error it does not conform to protocol");
}
return self;
}
- (void)forwardInvocation:(NSInvocation *)invocation{
NSString *selString = NSStringFromSelector(invocation.selector);
NSLog(#"Called %#",selString);
[invocation invokeWithTarget:self.object];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [self.object methodSignatureForSelector:selector];
}
#end
I have invoked it using
id <MyProtocol> delegate = (TestBO *)[[TestProxyHandler alloc] initWithProtocol:#protocol(MyProtocol) andObject:[TestBO class]];
[delegate testMessage];
But I am not able to make it work in Swift even the initialzier is showing that the message
TestHandler.swift
import Foundation
class TestHandler: NSProxy {
var object: AnyObject
convenience override init(`protocol`: Protocol, andObject clazz: AnyClass) {
if clazz.conformsToProtocol() {
self.object = clazz()
}
else {
NSLog("Error it does not conform to protocol")
}
}
}
Does anyone have any clue to do this in swift ??
EDIT:
In java you can create runtime implementation of a method using the Proxy.newProxyInstance call but can this be achieved in iOS ? using swift ? Any clue ?
Comparing with Objective C and Swift, Swift offers extremely limited access to runtime language access . So based on my research till now it can’t be done :(
I even tried subclassing the NSProxy class in swift but just couldn’t call the super.init and code never compiles but however same thing works in objective C
So I ended up doing this approach
I created a protocol using
#objc protocol SomeProt {
// Some method
}
Note the keyword #objc before protocol is essential else you would not be able to pass it as a variable, also adding #objc limits the usage of protocol to objective c runtime features so don’t expect to get full features of protocols in swift
public func someMethod(`protocol` : Protocol, implementation : AnyClass) {
let isImplemented : Bool = implementation.conformsToProtocol(`protocol`)
// some code
}
If you need to use it in some dictionary or places where it should conform to NSCopying class then use
NSStringFromProtocol
and
NSProtocolFromString
methods
Now I have wrote a objective c helper class to do the initialization
ObjcHelper.h
#import <Foundation/Foundation.h>
#interface ObjcHelper : NSObject
+(NSObject *)objectForClass:(Class)clazz;
#end
ObjcHelper.m
#import "ObjcHelper.h"
#implementation ObjcHelper
+ (NSObject *)objectForClass:(Class)clazz{
return [[clazz alloc] init];
}
#end
Now to use it
let prot : SomeProt = ObjcHelper.objectForClass(NSClassFromString("PROT_HANDLER_CLASS_NAME")) as! SomeProt
However in future if anyone can offer a better answer then please be sure to post it here
I'm writing some Swift classes that build upon functionality in our objective-c app. I have a objective-c class with a delegate that conforms to a protocol. I'm trying to call a method on that delegate from inside of a Swift class I'm simplified it down to this.
FredTestProtocol.h:
#protocol FredTestProtocol
- (void) dumbMethod;
#end
FredTestClass.h:
#import <Foundation/Foundation.h>
#import "FredTestProtocol.h"
#interface FredTestClass : NSObject <FredTestProtocol>
#property (nonatomic, weak) NSObject <FredTestProtocol> *delegate;
#end
FredTestClass.m:
#import "FredTestClass.h"
#implementation FredTestClass
- (void) dumbMethod
{
NSLog(#"Boy, this is a dumb method");
}
#end
FredSwiftClass.swift
import Foundation
class FredSwiftClass {
func test()
{
let ocObject = FredTestClass()
ocObject.delegate.dumbMethod() // Error occurs here.
}
}
The indicated line produces the error "'NSObject' does not have a method named 'dumbMethod'" I've tried a lot of ways to eliminate the error, to no avail. I'm sure I'm missing something really fundamental. Can someone tell me how I should go about calling the delegate method from Swift?
When Swift examines the property delegate it simply sees that is is an NSObject and the fact that you have noted that it implements a protocol is ignored. I can't find any specific documentation as to why this is the case.
You can address this in a couple of ways.
First, you can redefine your delegate property to use class anonymity, then Swift will just see it as some object that implements the protocol -
FredTestClass.h
#import <Foundation/Foundation.h>
#import "FredTestProtocol.h"
#interface FredTestClass : NSObject <FredTestProtocol>
#property id<FredTestProtocol> delegate;
#end
Then your Swift code will compile as written.
or you can leave your delegate definition as is and tell Swift that you want to access the delegate as an instance of an object that implements the protocol via downcast -
FredTestSwift.swift
import Foundation
class FredSwiftClass {
func test()
{
let ocObject = FredTestClass()
let theDelegate=ocObject.delegate as! FredTestProtocol
theDelegate.dumbMethod()
}
}
Pretty sure I've got it.
func test()
{
let ocObject = FredTestClass()
if let myDelegate = ocObject.delegate as? FredTestProtocol
{
myDelegate.dumbMethod()
}
}