3

I'm trying to create a Swift custom text editor (lite version) and got stuck on iOS 8 fontDescriptorWithSymbolicTraits bug, returning nil. Are there any Swift workarounds?

I've created a small project sample here , you can download and run the following scenario:

  1. Select a text
  2. Click on "B" button
  3. Click on "I" button

The app crashes, fontDescriptorWithSymbolicTraits returns nil :(

private func addOrRemoveTraitWithName(traitName: String, traitValue: UInt32) {
    let range = self.textEditor.selectedRange
    let currentAttributesDict = self.textEditor.textStorage.attributesAtIndex(range.location, effectiveRange: nil)
    let currentFont = currentAttributesDict[NSFontAttributeName]

    let fontDescriptor = currentFont?.fontDescriptor()
    let fontNameAttribute = fontDescriptor?.fontAttributes()[UIFontDescriptorNameAttribute]

    var changedFontDescriptor: UIFontDescriptor?

    if fontNameAttribute?.rangeOfString(traitName).location == NSNotFound {
      let existingTraitsWithNewTrait = UIFontDescriptorSymbolicTraits(rawValue: fontDescriptor!.symbolicTraits.rawValue | traitValue)
      changedFontDescriptor = fontDescriptor?.fontDescriptorWithSymbolicTraits(existingTraitsWithNewTrait)
    } else {

      let existingTraitsWithoutTrait = UIFontDescriptorSymbolicTraits(rawValue: fontDescriptor!.symbolicTraits.rawValue & ~traitValue)
      changedFontDescriptor = fontDescriptor?.fontDescriptorWithSymbolicTraits(existingTraitsWithoutTrait)

    }

    let updatedFont = UIFont(descriptor: changedFontDescriptor!, size: 0.0)

    var dict = [String : AnyObject]()
    dict[NSFontAttributeName] = updatedFont
    self.textEditor.textStorage.beginEditing()
    self.textEditor.textStorage.setAttributes(dict, range: range)
    self.textEditor.textStorage.endEditing()
}

I highly recommend to take a quick look at sample project

What options do I have?

I also seen that on Apple documentation the method that was used by other is missing from the SDK & documentation

What others say about this:

  1. Font Descriptor returns nil in iOS 8
  2. http://www.openradar.me/19922049
Community
  • 1
  • 1
el.severo
  • 2,202
  • 6
  • 31
  • 62

1 Answers1

3

You're right about this. The bug was fixed in iOS 8.3, fortunately. The only reliable workaround is to drop down to the level of Core Text and perform the trait application there. It's a pain, but it solves the problem.

Thus, for instance, before iOS 8.3, this doesn't work:

if let body = UIFont(name: "GillSans", size: 15),
    emphasis = body.fontDescriptor().fontDescriptorWithSymbolicTraits(.TraitItalic) {
        fbody = body
        femphasis = UIFont(descriptor: emphasis, size: 0)
}

So you have to import CoreText and drop down to that level:

if let body = UIFont(name: "GillSans", size: 15),
    result = CTFontCreateCopyWithSymbolicTraits(body as CTFont, 0, nil, .ItalicTrait, .ItalicTrait) {
        fbody = body
        femphasis = result as UIFont
}

Fortunately, Swift 1.2 and later is aware that UIFont and CTFont are now toll-free bridged. Before Swift 1.2, this was even more complicated! And of course in earlier systems, they were not toll-free bridged and this was still more difficult.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I'm sorry but I'm not sure where I should put your code – el.severo Mar 19 '16 at 01:09
  • Nowhere. I'm not writing your code for you. I'm just showing you the equivalent Core Text code for doing symbolic traits without using the broken font descriptor. – matt Mar 19 '16 at 03:27
  • Oh, thank you! Don't bother, I didn't mean to sound that way :) – el.severo Mar 19 '16 at 14:30
  • I still have a question regarding to your suggestion. How do I convert *`UIFontDescriptorSymbolicTraits`* to *`CTFontSymbolicTraits`* ? I'd get the *`UIFontDescriptorSymbolicTraits`* from *`UIFontDescriptor`* but I don't know how I can convert / type cast it – el.severo Mar 19 '16 at 20:04
  • You don't "convert". The code shows you what to do: you just call it and end up with a UIFont. – matt Mar 19 '16 at 20:10
  • but *`symTraitValue`* and *`symTraitMask`* shouldn't come from *`UIFontDescription`* ? – el.severo Mar 19 '16 at 22:16
  • That's up to you. The traits themselves are the exact same bitmask so there is nothing to convert. A UIFontDescriptor _is_ a CTFontDescriptorRef - they are toll-free bridged. – matt Mar 19 '16 at 22:27
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/106819/discussion-between-el-severo-and-matt). – el.severo Mar 19 '16 at 23:53