Swift CocoaPods Library in Objective-C Project - ios

I have found several online resources on how to include an Objective-C library into a Swift project and a few limited resources on how to do the reverse (which is what I'm after).
I have managed to get my project to compile and run based on the work I did here in this question: Swift CocoaPod Library in Objective-C Project Migration from Swift 3 to 4/5
However whenever I try to access anything from the Swift library in my ObjC project my app crashes with the following...
2020-01-29 14:42:09.756352-0700 Hyperion[13547:2723315] -[HGCircularSlider.RangeCircularSlider setStartPointValue:]: unrecognized selector sent to instance 0x1074088c0
2020-01-29 14:42:09.763045-0700 Hyperion[13547:2723315] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[HGCircularSlider.RangeCircularSlider setStartPointValue:]: unrecognized selector sent to instance 0x1074088c0'
*** First throw call stack:
(0x18e32ea48 0x18e055fa4 0x18e2325a8 0x1923cf86c 0x18e332af4 0x18e334a7c 0x10208f554 0x10208d4d4 0x192d15dec 0x192be580c 0x10208d388 0x10208b570 0x191d8cab8 0x191d8d160 0x191d11b40 0x191d11c6c 0x191d192c0 0x191d23b84 0x191d23f70 0x191d1716c 0x191d119a0 0x191d87134 0x191d87838 0x1924f3d74 0x1924f7224 0x1924f70d0 0x1924f739c 0x191d87290 0x191d87838 0x1924f3d74 0x1924f7224 0x1924f70d0 0x1924f739c 0x191d87290 0x191d87838 0x191cceaec 0x191ccdf08 0x191cca9c8 0x191cca7e0 0x191ccde54 0x1923a2e50 0x191b3e148 0x1923a2e50 0x191dd9494 0x191dd97f8 0x191b40b58 0x1923a2e50 0x191dd9494 0x191dd97f8 0x191dd8814 0x1923dc3d4 0x1923dd714 0x1923b9e2c 0x192431fa4 0x192434500 0x19242d374 0x18e2aca00 0x18e2ac958 0x18e2ac0f0 0x18e2a723c 0x18e2a6adc 0x19822c328 0x1923a1ae0 0x1020de520 0x18e130360)
libc++abi.dylib: terminating with uncaught exception of type NSException
As I state in the answer of my question linked above I have written a Swift class that exposes the CocoaPod's library functions I need which allows my project to compile...
import Foundation
import HGCircularSlider
#objc public class CircularSliderObjc: RangeCircularSlider
{
#objc override open var startPointValue: CGFloat {
get {
return super.startPointValue;
}
set {
super.startPointValue = newValue;
}
}
#objc override open var endPointValue: CGFloat {
get {
return super.endPointValue;
}
set {
super.endPointValue = newValue;
}
}
#objc override open var endThumbImage: UIImage? {
get {
return super.endThumbImage;
}
set {
super.endThumbImage = newValue;
}
}
#objc override open var startThumbImage: UIImage? {
get {
return super.startThumbImage;
}
set {
super.startThumbImage = newValue;
}
}
}
As you can see in the project I have exposed startPointValue as both a set and a get. However invoking this function at runtime causes the crash.
Here is the calling class's header...
#import "BaseViewController.h"
#import "Hyperion-Swift.h"
#interface TemperatureDeviceViewController : BaseViewController
#property (weak, nonatomic) IBOutlet CircularSliderObjc *rangeSlider;
And the call in my .m file...
[self.rangeSlider setStartPointValue:[self getSliderValueForTemp:startValue]];
I'm assuming I'm missing one critical step here?
UPDATE #1
I've tried the answer suggested by Max which makes a lot of sense. However because this is an IBOutlet it in my ViewController his solution really doesn't work for my use case here.
I did some further debugging and put breakpoints all over my startPointValue function in order to see what the "super" was referencing. However none of the breakpoints are triggered before the crash happens. Therefore I'm not sure that the call to "super" is the actual cause? Verdict is still out on this one I think.

Try redesigning your wrapper class to encapsulate rather than inherit from the framework class. For example:
#objc public class CircularSliderObjc
{
let slider: RangeCircularSlider // NOT #objc!
#objc var startPointValue: CGFloat {
get {
return slider.startPointValue;
}
set {
slider.startPointValue = newValue;
}
}
That way Objective-C doesn't even need to know that the Swift class exists at all. My guess is that this is crashing on the super, where something Swift-specific is being accessed from Objective-C.

Related

Why can't I set the speed of a GKAgent2D in iOS9?

If I try to change the value of the speed parameter of a GKAgent2D (or its parent class GKAgent) in iOS9 I get this unrecognised selector error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[GKAgent2D setSpeed:]: unrecognized selector sent to instance
However in iOS10 this error does not occur and the speed of the agent is changed correctly.
I can reproduce the error in a very simple example (single view iOS application, change the view controller code to the following, note that the error occurs with either GKAgent2D or GKAgent):
import UIKit
import GameplayKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var agent = GKAgent()
agent.speed = 10
print(agent.speed)
}
}
The above always crashes with this unrecognised selector error on the line that sets the agent's speed on the simulator running iOS9.3, but not iOS10.3 (all this under Xcode 8.3.2).
The speed property of GKAgent is documented as being read and write and being supported under iOS9 - see Apple's Documentation for GKAgent, speed Property.
I had a similar problem with GKAgentDelegate, which would crash on iOS9 only with an unrecognised selector to agentWillUpdate. I worked around this by putting a dummy method in my code:
func agentWillUpdate(_ agent: GKAgent) {
}
I have tried a similar solution to the new error, by overriding the speed property in my GKAgent2D sub class and providing an explicit setter and getter, and even backing the speed parameter by a private Float as suggested by Kdawg, but the same error still occurs when the super's speed parameter is set:
class Agent:GKAgent2D {
override var speed:Float {
get {return localSpeed}
set {localSpeed = newValue}
}
private var localSpeed:Float {
get {return super.speed}
set {super.speed = newValue}
}
}
Any thoughts?
Specifically: are there any known issues with GKAgent in iOS9 regarding selectors?
Alternatively, any thoughts on an alternative work around?
I would like to support iOS9 if I can. It looks to me like a bug in GameplayKit - but I expect Apple's response to a report would be that it is fixed in iOS10.
Edited
What I meant was to have your subclass's property setter/getter just override the base class's and not try to access the parent class's setter at all, like this:
class Agent:GKAgent2D {
private var _speed: Float = 0.0
override var speed:Float {
get {return _speed}
set {_speed = newValue}
}
}
At that point, assuming that the GKAgent and GKAgent2d accesses speed through its property getter, it will get the value of _speed from the overridden getter in your subclass. This is why I suggested in the comments to try this after making that subclass:
var agent: GKAgent
agent = GKAgent2dSubType()
agent.speed = 10
I would then expect that if you then tried to read out the property of your GKAgent agent, it would be 10. And so the expected behavior of your agent might work properly with your speed in there.
Original
This is just a total guess (I'm not really familiar with the technologies here), but have you tried changing your
override var speed:Float {
get {return super.speed}
set {self.speed = newValue}
}
to not reference super.speed or self.speed (incidentally, shouldn't setting self.speed from your speed setter result in an infinite recursive call to that setter?) but rather to reference a private backing floating point value? Then maybe you'll be able to instantiate an object of type GKAgent2dSubClass (however you've named that subclass) and have it work.

"unrecognized selector sent to instance" when setting a property on react-native

I'm trying to link my Swift view with my React-Native project. I figured out how to display it, but now when I'm trying to set a property, I'm having this error message:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Switch setMessage:]: unrecognized selector sent to instance 0x7f96b270de70'
In my react-native code, I'm doing:
const SwitchNative = requireNativeComponent('Switch', Switch);
class Switch extends Component {
render() {
return (
<SwitchNative message="message will be" style={this.props.style} />
);
}
}
Then doing this in my SwiftBridge:
// SwiftBridge.h
#import "RCTView.h"
#interface SwitchBridge : RCTView
#property (nonatomic, assign) NSString *message;
#end
// SwiftBridge.m
#import "RCTBridgeModule.h"
#import "RCTViewManager.h"
#import "SwitchBridge.h"
#interface RCT_EXTERN_MODULE(SwitchManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(message, NSString)
#end
Then finally I have this in my Swift class Switch.swift:
...
public func setMessage(message: String) {
NSLog("It's working well");
}
...
Not sure why it can't find my setMessage function.
React Native is trying to call -[Switch setMessage:] but you've defined -[Switch setMessage] without any parameters. Add a parameter to your Swift method signature:
public func setMessage(_ message: String)
You might also need to annotate your method with #objc depending on your version of Swift.
The correct syntax is now in swift,
#objc(setMessage:)
public func setMessage(message: String) {
NSLog("It's working well");
}
This is change for Swift > 3.2, Mac High Sierra and React Native >= 0.43
For anyone might be searching for passing an array of objects from React-Native (I ran into this issue while doing that):
YourView.h:
#interface YourView : RCTView
#property (nonatomic, assign) NSMutableArray *markers;
#end
YourView.m:
...
RCT_CUSTOM_VIEW_PROPERTY(markers, NSMutableArray, YourView)
{
[view setMarkers:json];
}
YourView.swift:
#objc(setMarkers:)
public func setMarkers(json: NSMutableArray) {
}

swift 2.1, xcode 7.2. simple NEWBIE about why it works in Playground but not in application

This code works in Playground, but I get a compile error when I define this in my project in Xcode 7.2
Here is my Playground screenshot
https://goo.gl/yJ4Q75
Error is: method does not override any method in the super class
public class A {
private func myUnavailableMethod() {
print("A. private func myUnavailableMethod()")
}
}
public class B : A {
override func myUnavailableMethod() {
print("B. func myUnavailableMethod()")
}
}
Motivation to this Playground was an error when trying to override a method, compiler was complaining as "Not available"
class MySFSafariViewController: SFSafariViewController {
override init() {
}
}
---- FOUND HOW THEY MARKED a method as unavailable.
When jumping to the Objective C declaration.
#interface SFSafariViewController : UIViewController
/*! #abstract The view controller's delegate */
#property (nonatomic, weak, nullable) id<SFSafariViewControllerDelegate> delegate;
****- (instancetype)init NS_UNAVAILABLE;****
The meaning of private/internal/public is different in Swift compared to some other languages.
IF and it is an IF you have your classes as two separate files in the project, then it's pretty clear.
private - scope is visibility is the file that holds the code
internal - scope of visibility is the namespace
public - scope of visibility is full access from anywhere
In Xcode Playground their are both in one file so the method is visible to class B.
The myUnavailableMethod of class A is private, therefore it can't be overridden. Change the method declaration to be internal by removing the private keyword.

Crashing on presenting IDMPhotoBrowser in Swift

I am integrating IDMPhotoBrowser in my Swift Project.
I have created a bridging header and I have imported IDMPhotoBrowser.
#import <IDMPhotoBrowser.h>
In my view controller:
class ViewController: UIViewController, IDMPhotoBrowserDelegate {
override func viewDidLoad() {
super.viewDidLoad()
var photoBro = IDMPhotoBrowser(photos: imagesArray)
photoBro.delegate = self
presentViewController(photoBro, animated: false, completion: nil)
}
But when it is executed, I get the following error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString unloadUnderlyingImage]: unrecognized selector sent to instance 0x165d6870'
In IDMPhotoBrowser Library, I found this function in IDMPhoto.m
// Release if we can get it again from path or url
- (void)unloadUnderlyingImage {
_loadingInProgress = NO;
if (self.underlyingImage && (_photoPath || _photoURL)) {
self.underlyingImage = nil;
}
}
From the error it looks like it is expecting to do something to an array of IDMPhotos but instead has an array of strings...are you passing the right kind of array to the constructor?
As #GoatInTHeMachine pointed, I was not passing the right kind of array.
But as I wanted to pass the imageURLs, I had to change the constructor.
The following worked for me:
var photoBro = IDMPhotoBrowser(photoURLs: imagesArray)

Can't inject property, setter selector not found

Using Typhoon and Swift, I am setting up my project and I have this problem. I have a class TPLAddInteractor this way
class TPLAddInteractor: NSObject, TPLAddInteractorInput {
var output: TPLAddInteractorOutput?
var dataManager: TPLDataManagerInterface?
}
My assembly looks like this
class TPLAddAssembly: TyphoonAssembly {
var applicationAssembly: TPLApplicationAssembly?
dynamic func addInteractor() -> AnyObject {
return TyphoonDefinition.withClass(TPLAddInteractor.self) {
(definition) in
definition.injectProperty("output", with: self.addPresenter())
definition.injectProperty("dataManager", with: self.applicationAssembly?.dataManager())
}
}
dynamic func addPresenter() -> AnyObject {
return TyphoonDefinition.withClass(TPLAddPresenter.self) {
(definition) in
definition.injectProperty("interactor", with: self.addInteractor())
}
}
}
And then I receive this error right after running the app:
reason: 'Can't inject property 'dataManager' for object '<TPL.TPLAddInteractor: 0x7ff5b2d2bcf0>'. Setter selector not found. Make sure that property exists and writable'
I am reading the Swift example of Typhoon and I don't see anything unusual in my code. But I am new on Swift so maybe I'm missing something.
Thanks
To work with Typhoon, it's necessary for Swift protocols to have the '#objc' directive. This is because Typhoon uses the Objective-C runtime as Swift has no native dynamism as yet.
This requirement is documented in the Swift Quick Start.
//Inject as follows it will give a warning but its working for me:
definition?.injectProperty(Selector(("cityInfo")), with: self.coreAssembly.cityInfo())

Resources