Changing the implementation of Objective C method in Swift subclass - objective-c

I am using an external library in my project. It is integrated via CocoaPods. I have access to the source code. The library is working well, but I need some modifications at some places in order for it to serve my purposes. It is written in Objective C. My project is in Swift.
I need to change the implementation of one method in the library. The problem is it is in the .m file and uses a lot of stuff declared only in the .m file. For example:
-(NSString*)methodToChange
{
NSArray<NSNumber*>* data = [self someInternalMethod:1];
uint value = data[0].unsignedIntValue;
return [self anotherInternalMethod:value];
}
I tried subclassing it like this:
class MySubclass : MySuperclassWithMethodToChange {
override var methodToChange: String {
//trying to use someInternalMethod and anotherInternalMethod
//unsuccessfully because they are not visible
}
}
The internal methods are using and modifying properties from the .m file that are also not accessible. Is there any way to solve this?

I would suggest forking the original library repository and making the necessary changes in your fork. You can then have your Podfile point to that. If your changes could be useful to others, make them in a way that doesn't break existing functionality and contribute them back to the library by opening a pull request.
If the original library gets updated later, you will have to merge those changes from the so-called "upstream" repository into yours. This does not happen automatically, so you'll have full control (and responsibility) over that process. See https://help.github.com/en/articles/syncing-a-fork for how this would look like.

Related

Swift CocoaPod Library in Objective-C Project Migration from Swift 3 to 4/5

I'm using a Swift CocoaPod in my Objective-C project. Originally it was a Swift 3 project and I simply had to add the #import "<Project Name>-Swift.h" into my .m or .h file. I've since updated to the latest version of the CocoaPod which I believe is now Swift 5. Therefore this is no longer working as none of the properties can be found.
I've looked at a number of different resources online including Apple's documentation and the procedure is unclear here what to do. It seems I have to go in and edit the .swift file and add #objc in front of each property or method I wish to access? This seems to go against what CocoaPods is about as the next time I update all my changes will be blown away.
I tried looking at the generated header but when I highlight the #import "<Project Name>-Swift.h" line I just get taken to the NSObject declaration. Obviously it is just a build time item I'm guessing.
According to the Apple documentation (https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_swift_into_objective-c) the Swift declarations must be marked with the public or open modifier and when I checked the swift file I found that they indeed are.
The pod I'm using is HGCircularSlider. An example of what I'm trying to access is in the RangeCircularSlider class is here:
open var startPointValue: CGFloat = 0.0 {
didSet {
guard oldValue != startPointValue else { return }
if startPointValue < minimumValue {
startPointValue = minimumValue
}
if distance > 0 {
endPointValue = startPointValue + distance
}
setNeedsDisplay()
}
}
As we can see here the declaration is public. I'm assuming I need to set some sort of build setting?
In addition I have set use_frameworks! in my Podfile. I've also checked that the build target within the pod project and Objective-C Generated Interface Header Name is HGCircularSlider-Swift.h. This is exactly what I'm using in my Objc .m file.
Welcome any thoughts or comments where else I should look?
Update 1
One other thing of note. In my .h and .m I have a #property of type RangeCircularSlider which it can see fine. No error there. It's accessing the properties and methods within the object that is causing the errors.
Property 'startPointValue' not found on object of type 'RangeCircularSlider *'
So it sees the swift file but as originally stated, no properties can be found.
Update 2
So I've discovered that if I add #objc to the front of the individual properties I'm trying to access in the Swift file the generated header file then see's them and I can therefore access them. So for the example function above it would then be
#objc open var startPointValue: CGFloat = 0.0 {
But these seems to be a bit of a hack as for when I want to install an updated version of this CocoaPod, then those changes I made to the pod's file would be blown away.
Any suggestions how to do this without having to edit the swift files in the CocoaPod?
For anyone else that runs across this. The online resources I found were helpful but didn't detail this specific case.
First off. Leave the CocoaPod code as it is. This will allow you to update it without having to worry about modifying it each time.
Next create a Swift file within your Objective C Project. xCode will ask if you want a header generated for it, say yes. This file will be called -Swift.h.
In this Swift file subclass the Swift file from the CocoaPod you are interested in accessing.
For example :
import Foundation
import HGCircularSlider
class CircularSliderObjc: RangeCircularSlider {
}
Next add in the properties you wish to access with getter and setters.
#objc override open var startPointValue: CGFloat {
get {
return super.startPointValue;
}
set {
super.startPointValue = newValue;
}
}
Then finally change the import in your Objective-C file to your project's generated header file that I mentioned above (#import "-Swift.h"
). If you have a property pointing to the class in the CocoaPod change it to your new Swift Class. For example :
#property (weak, nonatomic) IBOutlet CircularSliderObjc *rangeSlider;
In this example I have it setup as an Outlet for InterfaceBuilder.
After that, you're done. It seems like a lot of work but it's quite easy and simple. Not quite as fast as just importing the header but since Swift 4 and 5 you can no longer just access open vars in Swift as properties in objc. They need the #objc declaration to make that work. Added security to the language I'm guessing.
Hope this helps someone.

Why can't I access objective-c methods even though file is in bridge?

I have a project with a lot of objective-c code. Now I would like to access that code in my swift files. But for some reason I can't access the objective-c methods even though I can initiate an object of that class.
I have a bridge-file with all the .h files added called projectName-Bridge-Header.h
#import "NumberVerificationViewController.h"
And it's path is added in the build-settings (a side question: I can initiate an object from a class even though the bridge path isn't added to the build-settings. Why is that?):
I can initialize an object from the class, but I can't access the method:
var num = NumberVerificationViewController()
num.numberVerificationCallCompleted
The last line gives error that method does not exist.
I'm new to objective-c so it might be something really simple. Still, it's beyond me.
At first glance, it looks like you might be missing the declaration in the .h file to make the method public.
NumberVerificationViewController.h
-(void)numberVerificationCallCompleted:(NSNumber*)responseNumber;
NumberVerificationViewController.m
-(void)numberVerificationCallCompleted:(NSNumber*)responseNumber {
...
}

How do I use Objective-C code from Github in my Swift program?

Summary of problem
I'm building a calendar app in Swift, and I'd like to start with the UI in this repo: https://github.com/erichoracek/MSCollectionViewCalendarLayout
This repo is written in Objective-C.
However, I'm not sure how to build on top of this Github repo using Swift.
Example of the UI I'd like to use in my calendar:
What I've tried
I successfully installed this library with CocoaPods.
I ran $pod install.
I successfully ran import MSCollectionViewCalendarLayout.
I added a new CollectionViewController on my Storyboard, and assigned it to my custom CalendarViewController class
I tried (dumbly) with the code below in class CalendarViewController,but I get this error message:
"Cannot assign value of type 'MSCollectionViewCalendarLayout.Type' to type 'UICollectionViewLayout'"
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.collectionViewLayout = MSCollectionViewCalendarLayout
}
I understand I probably have to use self.collectionView.register(cellClass:forCellWithReuseIdentifier:)...but how?
There's some documentation titled "Usage" in the Github, but unfortunately I'm still lost as to how to use it:
There's also an Example.xcworkspace included, written in Objective-C. I've gone through most of it. They include header (.h) and implementation (.m) files for each of the elements that they use.
There's a good answer to a tangential problem here: https://stackoverflow.com/a/45540130/3979651, but this doesn't directly solve my problem. I'd like to import this Cocoapods library, instead of writing on top of Objective-C files. But I'm also not entirely sure if this is the right / best way to do it.
Summary of questions
How do I link my CollectionViewController to MSCollectionViewCalendarLayout so that it has the same UI?
How do I use self.collectionView.register(cellClass:forCellWithReuseIdentifier:) in this case?
Do I have to write a new file for each element, like in the Example? (Event cell, Day Column Header, etc.)
Or, would it be easier to just copy and paste the Example element files (.h and .m) into my app? If so, how do I build on top of those files?
What I'd like to accomplish
I would like to import MSCollectionViewLayout like a library, attach it to my own CollectionViewControllers, and build my own functionality on top of this, all using Swift.
Thank you all in advance! Hopefully I can commit the correct answers here to the repo's README.md file.
You can better use one of the below repos which are purely written in Swift and has the same UI as of MSCollectionViewLayout
1) CalendarKit
2) Calendar
Note: I personally used these libraries and these are easy to
integrate with your existing application.

Xcode pluginDidLoad not getting called when adding objective-c file in swift plugin

I'm working on a plugin for Xcode. It is supposed to be written in Swift.
When I start with a fresh plugin project (I'm using this Xcode Plugin template which is also available via Alcatraz) the project compiles and runs fine.
The pluginDidLoad method is getting called right after Xcode starts. As soon as I add any Objective-C file (and a bridging header of course) the pluginDidLoad method is not getting called anymore.
The Objective-C file might be as simple as an empty class that is a subclass of NSObject.
Removing the target-memberbership (for the plugin-target) from the newly created Objective-C (.m) file the aforementioned mentioned method is getting called again.
Has anyone developed a Xcode plugin in Swift that also uses Objective-C files before and got this working?
Update
It seems that my original solution only works with Swift only projects because Xcode always takes the objective c class if you have one.
So here is another trick: Extend the NSObject class by the function class func pluginDidLoad(bundle: NSBundle) {} and initialize your plugin there. Then it doesn't matter on which class it is called. You might have to check that also all Swift classes subclass NSObject. I pushed it to my repository that you can have a look
Original Post
I think I could reproduce the problem now. To simplify the problem, let's say that we have only two swift classes PluginMain and PluginHelper.
As you said, sometimes the plugin isn't getting called for some mysterious reason. I was struggling with the problem again and I was wondering how Xcode knows which class is the main class. So I came up with the idea to put the following initializer in both classes PluginMain and PluginHelper
class func pluginDidLoad(bundle: NSBundle) {
let appName = NSBundle.mainBundle().infoDictionary?["CFBundleName"] as? NSString
if appName == "Xcode" {
//sharedPlugin = SwiftySafe(bundle: bundle)
//initialize your shared plugin
}
}
By putting a breakpoint or log message in pluginDidLoad in both classes, I notices that Xcode isn't ignoring the plugin, it is just loading the wrong class (e.g. PluginHelper instead of PluginMain).
The Solution
It turns out that Xcode uses the class that is compiled first as the main class and calls pluginDidLoad only on that. So you can change that by reordering the "Compiled Sources" under your target settings->Build Phases. Move your main class so that it is on top. In the following image you find an example from my project. SwiftySafe is my main class.
My example
You will find my project here https://github.com/creinders/SwiftySafe if you want to compare the settings.
The pluginDidLoad method is called on the principal class. When the principal class is a Swift class, you have to include the module name in the NSPrincipalClass Info.plist key.
So if your target name is MyPlugin and your principal class is MyClass, set NSPrincipalClass to MyPlugin.MyClass.
Also make sure that MyClass inherits from NSObject.
Sometimes you need to tell Xcode to reload bundle. Run this and restart Xcode
defaults delete com.apple.dt.Xcode DVTPlugInManagerNonApplePlugIns-Xcode-7.3

How do i interface my objc program with an objc++ library?

I have an objc program and i would like to use a widget that is written in objc++ (namely https://launchpad.net/scintilla-cocoa). How do i go about this? Basically i want a new window controller object to interface with this objc++ library to define a scintilla text editor widget. Simply creating a new 'objc class' and accessing the library from there generates a bunch of errors related to the C++ class keyword and so on.
Thanks in advance
Since I'm the one who put you into the (hopefully rewarding :-)) trouble of using Scintilla, here I am.
Let's say we create a ScintillaView subclass, named ppScintillaEditor.
The file should have an .mm extension (e.g. ppScintillaEditor.mm)
The code would be roughly like this...
Interface
#import "Scintilla/ScintillaView.h"
#interface ppScintillaEditor : ScintillaView
{
// your iVars
}
// your properties / methods / whatever
Now, as for the implementation part, remember to put some initialization method to set up the view properly (as in the example accompanying Scintilla-cocoa; I mean the Test project)
Sidenote : Of course, you can create subclasses, categories or whatever on top the ScintillaView class, pretty much based on what you need - I, for example, have create a separate Category just in order to group there some ScintillaView specific commands (sooner or later, you'll notice that for some more advanced Scintilla manipulations, although it's there, it may need some polishing to be a bit more cocoa-friendly, so here you go...)
Now, last but not least...
To resolve the "bunch of errors related to the C++ class keyword and so on", as I've shown in my other video-response to your comment, all you have to do is :
Go to your project's Build Settings
Under Apple LLVM Compiler 3.0 - Preprocessing
Option Preprocessor Macros
Add to both Debug and Release :
SCI_NAMESPACE SCI_LEXER
And that's it. :-)
Hint : The above are defined by Scintilla to avoid clashes between C and non-C elements, like above... so, all it takes is to notify the preprocessor and the rest is taken care of....
you would create an objc class which has the interface your app needs, then implement and add the ivars and implement -- all behind a compilation firewall so the objc++ sources are not included in the header. your implementation would provide any necessary conversions.
it is like you have already done, but you remove the scintilla headers from the header for your wrapper -- they are visible only to your wrapper's implementation.
Update
To illustrate one possible approach:
MONScintillaWrapper.h
// no c++/scintilla sources should be included in this header
#import <Foundation/Foundation.h>
#interface MONScintillaWrapper : NSObject
- (void)setBackgroundColor:(NSColor *)pColor;
#end
MONScintillaWrapper.mm
#import "MONScintillaWrapper.h"
#implementation MONScintillaWrapper
{
scintilla::t_thing scintillaThing;
}
- (void)setBackgroundColor:(NSColor *)pColor
{
...convert pColor to a scintilla color and pass that to scintillaThing...
}
#end