Swift: How to Add a Class Method in 'string" Extension

Swift: How to add a class method in 'String extension

Class and static functions are not called on an instance of a class/struct, but on the class/struct itself, so you can't just append a string to a class.

Apple Documentation:

Within the body of a type method, the implicit self property refers to
the type itself, rather than an instance of that type.

You can, however, append a string to a variable instance of a String using the mutating keyword:

extension String {
mutating func aaa() {
self += "hello"
}
}

let foo = "a"
foo.aaa() // ERROR: Immutable value of type 'String' only has mutating members named 'aaa'

var bar = "b"
bar.aaa() // "bhello"

If you are trying to use a pointer to a string as a parameter, you can use the inout keyword to alter the inputed string:

extension String {
static func aaa(inout path: String) {
path += "Hello"
}
}

var foo = "someText"
String.aaa(&foo)
foo //someTextHello

Class function in swift extension (category)

Yes, it possible and very similar, the main difference is that Swift extensions are not named.

extension UIColor {
class func colorWithHexString(hexString: String) -> UIColor {
// create color from string
// ... some code
return newColor
}
}

Is it proper to add an extension to UIViewController class, or is there a preferred way of adding another method to an established Swift Class?

The answer are in the 2 comments from Vadian

Vadian:

It's a brilliant practice

Me:

Forgive the redundancy, so it's ok to do it this way, as opposed to
protocols, and delegates? Just checking since am still green around my
Swift gills. Also, how can I mark your answer as correct since it's
just a comment?

Vadian

It depends on your needs. Delegates are not appropriate because
usually two different classes are involved with the delegate pattern.
A protocol gives you finer control which objects should conform to,
but if you just want to extend the functionality of all instances of a
class an extension is the best choice. –

Why does my custom class not recognize swift extensions? (i.e. String)

How did you add the class with the extension? I added some classes into my project which included extensions, and the rest of the code wouldn't recognize the extensions. It appears that this was because I had added the files using "Create folder references" option instead of "Create groups" option. I deleted what I had added, then added them again with "Create groups" option selected. This fixed it for me.

I'm didn't pursue why using references might result in this behavior, because I had intended to add them with the "Create groups" option to begin with.

how to call function in Extension in swift?

You probably meant to extend RecipeTableViewCell, not DateFormatter.

extension RecipeTableViewCell {
func setTemplate(strDate: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "ydMMM", options: 0, locale: Locale(identifier: "ja_JP"))
let result = dateFormatter.string(from: Date())

return result
}
}

If you extend DateFormatter, the function setTemplate is added to the class DateFormatter -- you can't access it from RecipeTableViewCell. But if you really wanted to extend DateFormatter, you could do it like this:

extension DateFormatter {
func setTemplate(strDate: String) -> String {
self.dateFormat = DateFormatter.dateFormat(fromTemplate: "ydMMM", options: 0, locale: Locale(identifier: "ja_JP"))
let result = self.string(from: Date())

return result
}
}

/// usage:
let formatter = DateFormatter()
dateLabel.text = formatter.setTemplate(strDate: recipe.recordedAt)

Extending DateFormatter directly lets you access setTemplate from anywhere (not just RecipeTableViewCell), so you might want to do this.

Call String extension including function written in Swift from Objective-C

A Swift class, extension, or protocol can only be represented in Objective-C if it's convertible or marked explicitly with @objc.. which means it requires all parameters to be convertible to Objective-C as well.

struct also cannot be converted so that leaves us with just classes alone (IE: extensions on classes and concrete classes) and to mark a class @objc, it must inherit NSObject.

Example:

@objc
extension NSString {
func asImage(withAttributes attributes: [NSAttributedString.Key: Any]? = nil, size: CGSize = .zero) -> UIImage? {
let size = size == .zero ? self.size(withAttributes: attributes!) : size
if #available(iOS 10.0, *) {
return UIGraphicsImageRenderer(size: size).image { _ in
self.draw(in: CGRect(origin: .zero, size: size),
withAttributes: attributes!)
}
} else {
// Fallback on earlier versions
return nil
}
}
}

Notice that I changed the CGSize parameter to non-optional and compared against zero instead..

Also note that I marked it @objc and changed it to an extension on NSString because String itself cannot be converted to Objective-C as it's a struct!

Then in Objective-C you can call it like so:

[str asImageWithAttributes:@{....} size:CGSizeZero];

or whatever parameters you want..

Swift extension example

Creating an extension

Add a new swift file with File > New > File... > iOS > Source > Swift File. You can call it what you want.

The general naming convention is to call it TypeName+NewFunctionality.swift.

Sample Image

Example 1 - Double

Double+Conversions.swift

import Swift // or Foundation

extension Double {

func celsiusToFahrenheit() -> Double {
return self * 9 / 5 + 32
}

func fahrenheitToCelsius() -> Double {
return (self - 32) * 5 / 9
}
}

Usage:

let boilingPointCelsius = 100.0
let boilingPointFarenheit = boilingPointCelsius.celsiusToFahrenheit()
print(boilingPointFarenheit) // 212.0

Example 2 - String

String+Shortcuts.swift

import Swift // or Foundation

extension String {

func replace(target: String, withString: String) -> String {
return self.replacingOccurrences(of: target, with: withString)
}
}

Usage:

let newString = "the old bike".replace(target: "old", withString: "new")
print(newString) // "the new bike"

Here are some more common String extensions.

Example 3 - UIColor

UIColor+CustomColor.swift

import UIKit

extension UIColor {

class var customGreen: UIColor {
let darkGreen = 0x008110
return UIColor.rgb(fromHex: darkGreen)
}

class func rgb(fromHex: Int) -> UIColor {

let red = CGFloat((fromHex & 0xFF0000) >> 16) / 0xFF
let green = CGFloat((fromHex & 0x00FF00) >> 8) / 0xFF
let blue = CGFloat(fromHex & 0x0000FF) / 0xFF
let alpha = CGFloat(1.0)

return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
}

See here also.

Usage:

view.backgroundColor = UIColor.customGreen

Sample Image

Notes

  • Once you define an extension it can be used anywhere in your app just like the built in class functions.
  • If you are not sure of exactly what the function or property syntax should look like, you can Option+click a similar built in method. For example, when I Option+clicked UIColor.greenColor I see the declaration is class func greenColor() -> UIColor. That gives me a good clue for how to set up my custom method.
  • Apple Documentation for Extensions
  • In Objective-C extensions are known as categories.

How to add a method to a class at runtime in swift

You could add a compare method to NSNull like this:

Objective-C:

#import <objc/runtime.h>

static inline NSComparisonResult compareNulls(id self, SEL _cmd, NSNull *other) {
if([other isKindOfClass:[NSNull class]]) {
return NSOrderedSame; // Nulls are always the same.
}

return NSOrderedDescending;
}

@implementation NSNull (Comparisons)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
const char *encoding = [[NSString stringWithFormat:@"%s@:@", @encode(NSComparisonResult)] UTF8String];
class_addMethod([self class], @selector(compare:), (IMP)compareNulls, encoding);
});
}

@end

Swift:

// Add this code to your AppDelegate.swift file:
import ObjectiveC

fileprivate func compareNulls(_ self: AnyObject, _ _cmd: Selector, _ other: AnyObject) -> ComparisonResult {
if other is NSNull {
return .orderedSame
}

return .orderedDescending
}

fileprivate func addNSNullCompareImplementationIfNecessary() {
let sel = NSSelectorFromString("compareNulls:")
guard class_getMethodImplementation(NSNull.self, sel) == nil else {
return
}

let types = "i@:@"
class_addMethod(NSNull.self, sel, imp_implementationWithBlock(compareNulls), types)
}

// Add this line to your -didFinishLaunching: function:
addNSNullCompareImplementationIfNecessary()

This is only a temporary solution that will stop the crashes.

I would nevertheless encourage you to a) file a bug report, and b) continue investigating why this happened - clearly having an NSNull in this case wasn't expected by Parse...

How does one create a 'static' in a Swift class extension?

You can add static members to class extensions just the same as on classes. You need to prefix the class name to the static member name when you use it, e.g. NSDate.dateFormatterUTC, even if you’re using it in the same class.

This works:

extension NSDate {
private static let dateFormatterUTC: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
formatter.timeZone = NSTimeZone(abbreviation: "UTC")
return formatter
}()

public var UTC : String {
return NSDate.dateFormatterUTC.stringFromDate(self)
}
}

It’s also not the worst thing in the world just to use a private constant:

private let dateFormatterUTC: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
formatter.timeZone = NSTimeZone(abbreviation: "UTC")
return formatter
}()

extension NSDate {
public var UTC : String {
return dateFormatterUTC.stringFromDate(self)
}
}

This is not significantly worse than the static class member, because Swift’s private is file-private, not type-private. These two declarations of dateFormatterUTC have the same scope. Even in the first example, NSDate.dateFormatterUTC is accessible throughout the entire file it’s declared in.

I do agree that the static version is preferable, but for stylistic reasons only: I like the way it’s indented right next to the thing that uses it.

As Gwendal wisely notes above, this approach assumes UTC will only ever be called from one thread. Although static let and global let are both thread-safe in Swift, the NSDateFormatter class is not! Looks like it’s threadsafe starting in iOS 7. Phew.

Still, always good to keep a thread safety warning next to any mention of singletons. If you do want to use a non-threadsafe helper object from multiple threads, consider either creating a new helper on every call, or using NSThread.currentThread().threadDictionary to create a per-thread instance. Be sure to do a little profiling to make sure you’re actually solving a performance problem before opting for the more complex thread-local option.



Related Topics



Leave a reply



Submit