3

I wrote this in Xcode 8 Beta 6 (8S201h):

guard let faceMembers = NSFontManager.shared().availableMembers(ofFontFamily: familyName ?? fontName) else { return nil }

And it worked just fine. Now that I've upgraded to Xcode 8 GM Seed (8A218a) Xcode 8 (8A218a), it crashes (EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)).

Using the debugger to narrow it down, I found that something in NSFontManager.availableMembers(ofFontFamily:) really hates this, since it crashes no matter what I put in there, even with common (definitely installed!) fonts like Helvetica Neue.

(lldb) po NSFontManager.shared()
<NSFontManager: 0x6100000a24c0>

(lldb) po familyName
▿ Optional<String>
  - some : "Helvetica Neue"


(lldb) po fontName
"HelveticaNeue"


(lldb) po NSFontManager.shared().availableMembers(ofFontFamily: familyName ?? fontName)
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
The process has been returned to the state before expression evaluation.
(lldb) po NSFontManager.shared().availableMembers(ofFontFamily: familyName!)
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
The process has been returned to the state before expression evaluation.
(lldb) po NSFontManager.shared().availableMembers(ofFontFamily: "Not a real font?!")
nil

So when I pass it a valid font family name, it crashes... but when I pass it a fake one, it returns nil.

Is this a problem I can solve, or just an issue with Xcode 8 GM Seed Xcode 8 which will be solved in an SDK update?


After looking through the crash log, I saw this suspiciousness:

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libswiftFoundation.dylib        0x0000000107cbb249 _TZFE10FoundationSa26_forceBridgeFromObjectiveCfTCSo7NSArray6resultRGSqGSax___T_ + 153
1   libswiftCore.dylib              0x00000001079031f3 swift_dynamicCast + 1635
2   libswiftCore.dylib              0x000000010790448b _dynamicCastFromExistential(swift::OpaqueValue*, swift::OpaqueValue*, swift::TargetExistentialTypeMetadata<swift::InProcess> const*, swift::TargetMetadata<swift::InProcess> const*, swift::DynamicCastFlags) + 91
3   libswiftCore.dylib              0x0000000107903919 swift_dynamicCast + 3465
4   libswiftFoundation.dylib        0x0000000107d6a348 _TPA__TFFs15_arrayForceCastu0_rFGSax_GSaq__U_FQ_Q0_ + 56
5   libswiftFoundation.dylib        0x0000000107cbbc45 _TFEsPs10Collection3mapurfzFzWx8Iterator7Element_qd__GSaqd___ + 885
6   libswiftFoundation.dylib        0x0000000107cbb4c3 _TFs15_arrayForceCastu0_rFGSax_GSaq__ + 227
7   libswiftFoundation.dylib        0x0000000107cbb7a5 _TZFE10FoundationSa36_unconditionallyBridgeFromObjectiveCfGSqCSo7NSArray_GSax_ + 197

So it seems like it's crashing within Swift-Foundation, in some function called _forceBridgeFromObjectiveC... not sure if that helps anyone but it does confirm it's within the SDK/runtime.

Ky -
  • 30,724
  • 51
  • 192
  • 308
  • I submitted this as Apple Bug `28209297` – Ky - Sep 08 '16 at 17:23
  • My submitted Apple Bug was marked as a duplicate of `28195947`, which is still open. – Ky - Sep 15 '16 at 12:36
  • Did you submit a radar for this? Apple bug management is a complete nightmare and we will never know the outcome. If not I will submit one – stone Sep 18 '16 at 07:45
  • @livingstonef I submitted via Apple Radar, not OpenRadar, if that's what you mean. – Ky - Sep 19 '16 at 12:14

2 Answers2

2

The only way that I could figure out how to work around this in the meantime was to create a static method in an objective-c class. I then imported the header into my bridging header and called the static method from Swift 3, where it worked fine.

Hope this helps you get through these difficult times!

#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>

@interface WorkAround : NSObject

+ (NSArray *)typefacesForFontFamily:(NSString *)family;

@end


#import "WorkAround.h"

@implementation WorkAround

/// Returns an array of arrays, or nil, that contain information about
/// each typeface found for the specified font family.
+ (NSArray *)typefacesForFontFamily:(nonnull NSString *)family {
  NSFontManager *fontManager = [NSFontManager sharedFontManager];
  return [fontManager availableMembersOfFontFamily:family];
}
@end
definitelyokay
  • 377
  • 2
  • 14
1

Joseph E.'s answer is a good starting point. However to get it to work in Swift 3, Xcode 8 (8A218a) I had to approach differently...

  1. Subclass NSFontManager (in Objective C), and create the bridging header if you don't already have one. enter image description here enter image description here

Make sure to change the language to (Objective C). Its important that you do it this way, as it appears you cannot directly create an objective c m files?, even though there is an option to do so.

  1. Implementation

FontManager.h

#import <Cocoa/Cocoa.h>

@interface FontManager : NSFontManager
    NS_ASSUME_NONNULL_BEGIN

    + (NSArray *)typefacesForFontFamily:(NSString *)family;


    NS_ASSUME_NONNULL_END
@end

FontManager.m

#import "FontManager.h"

@implementation FontManager
    NS_ASSUME_NONNULL_BEGIN

    + (NSArray *)typefacesForFontFamily:(nonnull NSString *)family {
        NSFontManager *fontManager = [self sharedFontManager];
        return [fontManager availableMembersOfFontFamily:family];
    }

    NS_ASSUME_NONNULL_END
@end

bridging-header.h

//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "FontManager.h"
  1. Usage in your Swift 3 project

    if let fontMembers = FontManager.typefaces(forFontFamily: "Arial") as? [[Any]]  { }
    
Community
  • 1
  • 1
stone
  • 2,192
  • 16
  • 26