How to override Objective-c class functions in Swift? - objective-c

I'm currently trying to override the systemFontOfSize method of UIFont to return my custom font with the given font size. My code currently looks like this:
extension UIFont
{
class func systemFontOfSize(fontSize: CGFloat) -> UIFont
{
return UIFont(name: "HelveticaNeue", size: 10)!
}
}
However, I am unable to override that method. When using the code shown above, I get the following error:
"Method 'systemFontOfSize' with Objective-C selector 'systemFontOfSize:' conflicts with previous declaration with the same Objective-C selector"
When adding override, the error is that the method is not overriding anything.
Does anyone know how to fix this issue?

You can use Objective C extension as follows, if it fits your needs.
Earlier discussion related to this technique with my source code as an answer can be found here: Is there a way to change default font for your application.
All you have to do is to implement the class with the code mentioned at the link above and include the following header file in your umbrella header to be able to make the code be applied when calling methods in Swift code.
Header file UIFont+Utils.h:
#interface UIFont (Utils)
+ (UIFont *)systemFontOfSize:(CGFloat)size;
+ (UIFont *)lightSystemFontOfSize:(CGFloat)size;
+ (UIFont *)boldSystemFontOfSize:(CGFloat)size;
+ (UIFont *)preferredFontForTextStyle:(NSString *)style;
#end

You can use the #selector to redirect UIFont calls go to custom functions.
For best way how to replace font functions for whole iOS app, please check the below SO question, look for answer from - Javier Amor Penas: Set a default font for whole iOS app?

class FontClass: UIFont{
override class func systemFontOfSize(fontSize: CGFloat) -> UIFont
{
return UIFont(name: "HelveticaNeue", size: 10)!
}
}
In the project, you can use it like this:
FontClass.systemFontOfSize(10)
The hope can help you!

From Apple's documentation on extensions:
NOTE
Extensions can add new functionality to a type, but they cannot override existing functionality.
You cannot overwrite existing methods in an extension, you can only add new ones.

to be able to override the function, you must first subclass UIFont. what are you trying to do, is impossible. you are trying to redefine function in extension. so, i have no positive message, but that is the reality.
PS: if you are an Objective C 'expert', some hack could be available ...

Related

What does #objc dynamic var mean in Swift 4

Could you briefly explain what #objc and dynamic mean in Swift 4 using Xcode 9.x?
With tries and errors and following articles in the stackoverflow, I have eventually achieved this snippet to work. But I would like to know a little bit about those magical keywords.
class SampleViewController: NSViewController {
#objc class Parameters : NSObject {
#objc dynamic var value1: Double = 0 // bound to Value of a NSTextfield with NumberFormatter
#objc dynamic var value2: Double = 0 // as "parameters.value1" for the Model Key Path
}
#objc dynamic var parameters = Parameters()
#objc dynamic var value3: Double { // in the similar way as "value3" for the Model Key Path
get {
return parameters.value1 + parameters.value2
}
}
override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
switch key {
case "value3" :
return Set(["parameters.value1", "parameters.value2"])
default:
return super.keyPathsForValuesAffectingValue(forKey: key)
}
}
}
Having fun with Xcode and its disassembler, I have found some. Thanks to Mike Henderson's comment.
Firstly, adding a #objc modifier seems to have the compiler write its corresponding symbol name in a __OBJC segment of executables and/or library files, which will be then used by the Objective-C run-time system.
otool -o filename command shows us the contents of __OBJC segment.
Secondly, adding a dynamic modifier seems to have the compiler insert additional assembler codes to interact with the Objective-C run-time system. The additional code realizes that accessing dynamic properties will be done through objc_msgSend() and its related functions. Similarly, calling dynamic methods also will be done through objc_msgSend().
Now, in my understandings, the jargon dynamic dispatch implies use of objc_msgSend() while static dispatch does no use of it. In the latter case, both accessing variables and calling functions will be done without intervention of the Objective-C run-time system, which is in the similar, but not exactly same, way of C++ ABI.
Apparently, static one is faster than dynamic one. But static one is incapable of Objective-C's magical benefits, though. With the programming language Swift, we have opportunities to utilize both aspects by choosing either static or dynamic dispatch depending on the situation, by omitting or adding those magical keywords, respectively.
Thanks!
Further readings:
Objective-C Runtime
Using Swift with Cocoa and Objective-C (Swift 4.0.3)
#objc means you want your Swift code (class, method, property, etc.) to be visible from Objective-C.
dynamic means you want to use Objective-C dynamic dispatch.
Swift 3 - dynamic vs #objc

Swift default value for Objective-C selector parameter

I have the following Objective-C method:
-(void)setPaneState:(MHPaneState)state withInitialVelocity:(CGPoint)velocity{
self.paneState = state;
[self animatePaneWithInitialVelocity:velocity];
}
Currently, in Swift code in the same app, I'm calling this method like this:
setPaneState(.closed, withInitialVelocity: .zero)
I'd like for the 2nd parameter (velocity) to have a default value of CGPointZero. I know that Objective-C doesn't support default values at the language level, but Swift does, so I'd like to call this method in Swift like this:
setPaneState(.closed)
How can I accomplish this short of defining a new method that takes just the one MHPaneState parameter?
Could you write a Swift extension on your Objective-C type and give it a different name?
extension MyObjCThing {
func mySetPaneState(_ state: MHPaneState, withInitialVelocity velocity: CGPoint = .zero) {
setPaneState(state, withInitialVelocity: velocity)
}
}

Extending a Swift class with Objective-C category

Im in a situation where I need to use Objective-C category to extend a Swift class. I've done something as follows:
In "SomeClass.swift":
class SomeClass: NSObject {
}
In "SomeClass+Extension.h":
#import "Project-Swift.h"
#interface SomeClass (Extension)
-(void)someMethod();
#end
This has worked well. And if I try to use the SomeClass extension in my Objective C code, it is fine.
The problem is, if I want to use someMethod() in a another Swift class, I will need to put the SomeClass+Extension.h file into my ObjC-BridgingHeader.h file.
But doing this will cause a circular dependency, because SomeClass+Extension.h also imports Project-Swift.h.
Does anyone have a good way to get around this?
Please note that simply forward declaring the class in the category header will not work, as categories cannot use forward declarations for it's own implementation as so:
#class SomeClass without importing Project-Swift.h will give a compile error.
The Bad
i too have been fighting this issue a bunch. unfortunately the documentation pretty explicitly states that this pattern is not allowed:
To avoid cyclical references, don’t import Swift code into an
Objective-C header (.h) file. Instead, you can forward declare a Swift
class or protocol to reference it in an Objective-C interface.
Forward declarations of Swift classes and protocols can only be used
as types for method and property declarations.
also throughout the the linked page you will notice it keeps mentioning to import the generated header specifically into the .m file:
To import Swift code into Objective-C from the same target
Import the Swift code from that target into any Objective-C .m file
within that target
The Good
one solution that may work for you is to create a swift extension that redefines each method you need in the category. it is fragile and ugly, but arguably the cleanest solution.
/**
Add category methods from objc here (since circular references prohibit the ObjC extension file)
*/
extension SomeClass {
#nonobjc func someMethod() {
self.performSelector(Selector("someMethod"))
}
}
adding the #noobjc to the front allows the
same method signature to be used w/o overriding the ObjC implementation
now the import "SomeClass+Extension.h" from the bridging
header can be removed
if support for more than two input params is needed, or tighter type coupling is desired i would recommend using the runtime to call the underlying function. a great description is here.
From the Interoperability guide, we cannot directly access the subclassed / categorized / extensioned Objc-objects for the .swift [SomeClass] class.
But as a turn-around, we can do this:
For Variables , we can do this:
extension Class {
private struct AssociatedKeys {
static var DescriptiveName = "sh_DescriptiveName"
}
var descriptiveName: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.DescriptiveName,
newValue as NSString?,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
}
For Methods, we can use method_swizzling which is not recommended.
As one simple solution, you can move the extension to your Swift code. Then you won't have any dependency problems.

Call an Obj-C multi-input method in the Swift

I want to call an Objective-C multi-input method in Swift, for instance:
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag
{
NSLog(#"Did send with tag");
}
How could I call this method? By using "udpSocketdidSendDataWithTag"? It doesn't work.
It's swift would likely be something similar to this:
func udpSocket(_ sock:GCDAsyncUdpSocket, didSendDataWithTag tag: long) {
// use parameters 'sock' and 'tag'
}
And you'd call it like this:
udpSocket(socket, didSendDatawithTag:tag)
There are also many resources now on Objective-C and Swift interoperability. A quick search of google or Apple's documentation will reveal solid answers.
[Edit]
As user Thomas Killan mentioned, don't forget to setup the Interoperability by using a Bridging Header. Apple has great documentation on how to do this.

Swift to Objective-C header does not contain Swift classes

I'm attempting to use Swift classes in my Objective-C code, however my Swift classes don't seem to appear in the generated header. As a result, my build fails with "Use of undeclared identifier 'HelloWorld'".
I used the templates to create a project called TestApp.
I have the following Build Settings in my target:
Product Name : TestApp
Product Module Name : TestAppModule
Defines Module : Yes
Apple's documentation says to use #import <TestApp/TestAppModule-Swift.h> but this doesn't work.
Instead, I'm using #import "TestAppModule-Swift.h" in my ".m" file. It seems to find this.
I'm able to navigate to it, and it looks like this...
// Generated by Swift version 1.0 (swift-600.0.34.4.5)
#if defined(__has_include) && __has_include(<swift/objc-prologue.h>)
# include <swift/objc-prologue.h>
#endif
...etc...
but no classes defined in there.
I have a Swift file in the project that looks like this...
class HelloWorld {
func hello() {
println("hello world")
}
}
Why isn't this working using the standard header file location #import <TestApp/TestAppModule-Swift.h>?
How can I get my swift classes in that header file, so I won't get the "undeclared identifier" error?
Here's how I have gotten it to work. You can see a more large-scale answer here.
Change this:
class HelloWorld {
func hello() {
println("hello world")
}
}
To:
#objc class HelloWorld {
class func newInstance() -> HelloWorld {
return HelloWorld()
}
func hello() {
println("hello world")
}
}
Then, In your ObjC file:
#import "TestApp-Swift.h"
And call like this:
HelloWorld * helloWorld = [HelloWorld newInstance];
[helloWorld hello];
tl;dr Ensure you have a bridging header if you're doing any cross-calling between Objective-C and Swift.
I had the exact same problem: I could see the -Swift.h file in DerivedData but it made no mention of my Swift classes. I was importing the header file correctly, the Defines Module setting was YES, and the Product Module Name was correct. I tried deleting and re-adding the Swift files, clean buiild, quitting XCode, etc, with no luck.
Then I realised I had no -Bridging-Header.h file in my project, presumably due to the way I'd cobbled it together from a previous project. Shouldn't be a problem because I was not (yet) calling Objective-C from Swift. But when I added a bridging header, and referred to its path in the build settings (Swift Compiler - Code Generation -> Objective-C Bridging Header), it magically fixed the problem - my -Swift.h file was suddenly full of SWIFT_CLASS() goodness!
So I'm guessing the bridging header is fundamental to the process, even if you're NOT using Objective-C from Swift.
UPDATE: I finally understand this. It is related to public/internal access modifiers. Not sure if I missed this originally or if it's an addition to the Apple docs, but it now clearly states:-
By default, the generated header contains interfaces for Swift
declarations marked with the public modifier. It also contains those
marked with the internal modifier if your app target has an
Objective-C bridging header.
It is proper to use #import "TestAppModule-Swift.h" in your .m files. If you need to reference a class in a .h, use the #class forward declaration.
Further, if you want to use a Swift class from Objective-C, the Swift class must be marked with the #objc attribute. Xcode will only include classes with that attributed in the generated header. See also this documentation.
Class should be declared as #objc public class
A more convenient way would be to inherit from NSObject. Like so:
class HelloWorld: NSObject {
func hello() {
println("hello world")
}
}
In my case, by following Apple guidelines, it did not work until I ran the project. The xcode editor kept flagging the unknown swift class, until i clicked "run". The build succeeded, and the swift method worked.
In my case the class was not being compiled, because I first added it to my test target only... After adding it to my main target (Build Phases -> Compile Sources), it was actual compiled and added to the header file.
So much for TDD ;-)
Maybe you defined a Swift class with the same name as an existing Objective-C class which wouldn't be unusual if you want to refactor your Objective-C code to Swift.
As long as you have a class defined simultaneously in Swift and Objective-C the compiler quietly stops updating the bridging header altogether ("ProductModuleName-Swift.h") - which also affects subseqeuent changes in other bridged Swift files.
For general reference how to import Swift into Objective-C see:
Importing Swift into Objective-C | Apple Developer Documentation