Search code examples
iosswiftstringgenericsnsattributedstring

How do I use a use a generic type for both String and NSAttributedString?


I have an extension that sets bold to a String.

extension NSAttributedString {
    convenience init(format: NSAttributedString, makeBold: [String], regularText: [String]) {
        let mutableString = NSMutableAttributedString(attributedString: format)
        
        makeBold.forEach { string in
            let range = NSString(string: mutableString.string).range(of: "%bold%")
            let attributedString = NSAttributedString(string: string, attributes: [.font: UIFont.openSans(style: .bold, size: 15)])
            mutableString.replaceCharacters(in: range, with: attributedString)
            
        }
        
        regularText.forEach { text in
            let textRange = NSString(string: mutableString.string).range(of: "%regular%")
            mutableString.replaceCharacters(in: textRange, with: text)
        }
        self.init(attributedString: mutableString)
    }
}

It works fine, but I need the regularText: [String] to be able to accept [NSAttributedString]. I tried to do it like this:

convenience init<T>(format: NSAttributedString, makeBold: [String], regularText: [T])

but this gives me an error here:

mutableString.replaceCharacters(in: textRange, with: text)

with the error text

No exact matches in call to instance method 'replaceCharacters(in:with:)'

Not being too familiar with generics I'm not sure how to make this work. Any help would be appriciated.

I read here No exact matches in call to instance method error message in Swift

that it's a general error for using the wrong type, so I guess I havn't gotten generics right.

EDIT If I change regularText type from String to NSAttributedString it works without any errors, so why doesn't a generic work for both?


Solution

  • UPDATE (see comments): Add another convenience init

    convenience init(format: NSAttributedString, makeBold: [String], regularText: [NSAttributedString]) {
        self.init(format: format, makeBold: makeBold, regularText: regularText.map{ $0.string })
    }
    

    OR Just add another convenience init

    convenience init(format: NSAttributedString, makeBold: [String], regularText: [NSAttributedString]) {
        self.init(format: format, makeBold: makeBold, regularText: regularText.asStringArray)
    }
    

    and an extension like this

    extension Array where Element: NSAttributedString {
        var asStringArray: [String] {
            var strings: [String] = []
            
            self.forEach { text in
                strings.append(text.string)
            }
            
            return strings
        }
    }