24/7 twenty-four seven

iOS/OS X application programing topics.

NSArrayやNSDictionaryからNSNullを効率よく取り除く

iOSアプリケーションでWeb APIから返ってきたJSONを処理するのにNSNullの扱いに困っていて、事前にNSNullを取り除いてしまうのが事故を防ぐための確実な方法なのですが、再帰的にすべての要素を検査する以外になにかいい方法がないかと思って考えていたらちょっとおもしろい方法を思いついたので書いてみました。


kishikawakatsumi/CollectionUtils · GitHub

↑ に含まれるCUCompactArrayとCUCompactDictionaryです。


NSArrayとNSDictionaryのサブクラスとして実装されていて、次のようにして生成します。
(普通にalloc/initを使って生成することも可能です)

NSArray *array = @[@"0", @"1", [NSNull null], @"2", [NSNull null], @"3"];
NSArray *compactArray = [array cu_compactArray];
//=> ["0", "1", "2", "3"]
NSDictionary *dictionary = @{@"one": @"1",
                             @"null": [NSNull null],
                             @"two": @"2",
                             @"three": @"3"};
NSDictionary *compactDictionary = [dictionary cu_compactDictionary];
 //=> {"one": "1", "two": "2", "three": "3"}


JSONオブジェクトに対して使われることを想定しているので、ネストしたコレクションに対しても有効です。

NSArray *array = @[@"0",
                   @"1",
                   [NSNull null],
                   @"2",
                   @{@"one": @"1",
                     @"null": [NSNull null],
                     @"two": @"2",
                     @"three": @"3"},
                   @"4"];
NSMutableArray *compactArray = [array cu_compactArray];
//=> ["0", "1", "2", {"one": "1", "two": "2", "three": "3"}, "4"]


動作の仕組みは、まず与えられたNSArrayかNSDictionaryの最初の階層についてだけ、NSNullをチェックして取り除きます。
ここで、チェックされるのはあくまでもネストした最初の階層についてだけです。

- (instancetype)initWithObjects:(const id [])objects count:(NSUInteger)cnt
{
    self = [super init];
    if (self) {
        NSNull *nul = [NSNull null];
        NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:cnt];
        for (NSUInteger i = 0; i < cnt; i++) {
            id object = objects[i];
            if (object && object != nul) {
                [mutableArray addObject:object];
            }
        }
        _original = mutableArray;
        
    }
    return self;
}


そして、元のNSArrayかNSDictionaryを保持して同様の振る舞いをしつつ、実際にネストしたNSArray/NSDictionaryにアクセスがあったときに、適宜同じようにNSNullを削除したラッパーを返すことで、最終的にすべてのネストされた要素からNSNullが取り除かれるという結果になります。

#pragma mark - primitive instance methods

- (NSUInteger)count
{
    return self.original.count;
}
- (id)objectAtIndex:(NSUInteger)index
{
    id object = self.original[index];
    if ([object isKindOfClass:[CUCompactArray class]] || [object isKindOfClass:[CUCompactDictionary class]]) {
        return object;
    }
    if ([object isKindOfClass:[NSArray class]]) {
        return [CUCompactArray arrayWithArray:object];
    }
    if ([object isKindOfClass:[NSDictionary class]]) {
        return [CUCompactDictionary dictionaryWithDictionary:object];
    }
    return object;
}


このようにすることで、最初にすべての要素をチェックする方法だとNSNullがたとえ1つも含まれていなかったり、その値にアクセスすることがなくても、同じだけ処理時間がかかっていたのですが、実際にアクセスのあったタイミングで必要なところだけ処理することで、パフォーマンスに対するペナルティは非常に小さくなります。

けっこうおもしろいアイデアかなと思っているのですがいかがでしょうか?

追加ダウンロードフォントを含むiOS 7で使えるフォント一覧

出力の方法

iOS 7からは`kCTFontDownloadableAttribute`という属性が追加されているのでそれを利用します。
この方法で数えると、すべてのフォント数は283になりました。
iOS 7で新たに使えるようになった日本語のフォントとしてはOsakaとOsaka-等幅フォントがあります(追加ダウンロードフォント)。
かつてのMacの日本語システムフォントです。懐かしいですね。

+ (void)downloadableFonts
{
    NSDictionary *attributes = @{(id)kCTFontDownloadableAttribute : (id)kCFBooleanTrue};
    CTFontDescriptorRef fontDescriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)attributes);
    CFArrayRef matchedFontDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(fontDescriptor, NULL);
    
    NSMutableDictionary *familyNames = [[NSMutableDictionary alloc] init];
    NSInteger numberOfFonts = 0;
    NSMutableString *text = [[NSMutableString alloc] init];
    for (UIFontDescriptor *fontDescriptor in (__bridge NSArray *)matchedFontDescriptors) {
        NSString *familyName = fontDescriptor.fontAttributes[UIFontDescriptorFamilyAttribute];
        NSString *displayName = fontDescriptor.fontAttributes[UIFontDescriptorVisibleNameAttribute];
        NSString *postscriptName = fontDescriptor.postscriptName;
        
        if (!familyNames[familyName]) {
            familyNames[familyName] = familyName;
            [text appendFormat:@"<b>%@</b>\n\n", familyName];
        }
        NSMutableDictionary *fontDict = [NSMutableDictionary dictionary];
        fontDict[@"displayName"] = displayName;
        fontDict[@"postscriptName"] = postscriptName;
        fontDict[@"descriptor"] = fontDescriptor;
        NSArray *languages = fontDescriptor.fontAttributes[@"NSCTFontDesignLanguagesAttribute"];
        fontDict[@"languages"] = [languages componentsJoinedByString:@", "];
        
        [text appendFormat:@"- %@ \"%@\" [%@]\n", postscriptName, displayName, [languages componentsJoinedByString:@", "]];
        
        numberOfFonts++;
    }
    
    NSLog(@"%@", text);
}

フォント一覧(日本語フォントは[ja])

AlFirat

  • Al-Firat "Al-Firat" [ar]

AlKhalil

  • Al-KhalilBold "Al-Khalil Bold" [ar]
  • Al-Khalil "Al-Khalil" [ar]

AlRafidain

  • Al-Rafidain "Al-Rafidain" [ar]

Al Bayan

  • AlBayan "Al Bayan Plain" [en]
  • AlBayan-Bold "Al Bayan Bold" [en]

Algiers

  • Algiers "Algiers" [ar]

AlRafidain AlFanni

  • AlRafidainAlFanni "Al-Rafidain Al-Fanni" [ar]

Al Tarikh

  • AlTarikh "Al Tarikh" [en]

Andale Mono

  • AndaleMono "Andale Mono" [en]

Apple Chancery

  • Apple-Chancery "Apple Chancery" [en]

Apple Braille

  • AppleBraille "Apple Braille" [en]
  • AppleBraille-Outline6Dot "Apple Braille Outline 6 Dot" [en]
  • AppleBraille-Outline8Dot "Apple Braille Outline 8 Dot" [en]
  • AppleBraille-Pinpoint6Dot "Apple Braille Pinpoint 6 Dot" [en]
  • AppleBraille-Pinpoint8Dot "Apple Braille Pinpoint 8 Dot" [en]

AppleGothic

  • AppleGothic "AppleGothic Regular" [ko]

AppleMyungjo

  • AppleMyungjo "AppleMyungjo Regular" [ko]

Apple SD Gothic Neo

  • AppleSDGothicNeo-Bold "Apple SD Gothic Neo Bold" [ko]
  • AppleSDGothicNeo-ExtraBold "Apple SD GothicNeo ExtraBold" [ko]
  • AppleSDGothicNeo-Heavy "Apple SD Gothic Neo Heavy" [ko]
  • AppleSDGothicNeo-Light "Apple SD Gothic Neo Light" [ko]
  • AppleSDGothicNeo-Medium "Apple SD Gothic Neo Medium" [ko]
  • AppleSDGothicNeo-Regular "Apple SD Gothic Neo Regular" [ko]
  • AppleSDGothicNeo-SemiBold "Apple SD Gothic Neo SemiBold" [ko]
  • AppleSDGothicNeo-Thin "Apple SD Gothic Neo Thin" [ko]
  • AppleSDGothicNeo-UltraLight "Apple SD Gothic Neo UltraLight" [ko]

Apple Symbols

  • AppleSymbols "Apple Symbols" [en]

Arial Black

  • Arial-Black "Arial Black" [en]

Arial

  • Arial-BoldItalicMT "Arial Bold Italic" [en]
  • Arial-BoldMT "Arial Bold" [en]
  • Arial-ItalicMT "Arial Italic" [en]
  • ArialMT "Arial" [en]

Arial Narrow

  • ArialNarrow "Arial Narrow" [en]
  • ArialNarrow-Bold "Arial Narrow Bold" [en]
  • ArialNarrow-BoldItalic "Arial Narrow Bold Italic" [en]
  • ArialNarrow-Italic "Arial Narrow Italic" [en]

Arial Unicode MS

  • ArialUnicodeMS "Arial Unicode MS" [en]

Ayuthaya

  • Ayuthaya "Ayuthaya" [en]

Baghdad

  • Baghdad "Baghdad" [en]

Bangla MN

  • BanglaMN "Bangla MN" [en]
  • BanglaMN-Bold "Bangla MN Bold" [en]

Basra

  • Basra-Bold "Basra Bold" [ar]
  • Basra "Basra" [ar]

Beirut

  • Beirut "Beirut" [en]

Big Caslon

  • BigCaslon-Medium "Big Caslon Medium" [en]

Book Antiqua

  • BookAntiqua "BookAntiqua" [en]
  • BookAntiqua-Italic "BookAntiqua-Italic" [en]
  • BookAntiqua-BoldItalic "BookAntiqua-BoldItalic" [en]
  • BookAntiqua-Bold "BookAntiqua-Bold" [en]

Bookman Old Style

  • BookmanOldStyle "Bookman Old Style" [en]
  • BookmanOldStyle-Italic "Bookman Old Style Italic" [en]
  • BookmanOldStyle-BoldItalic "Bookman Old Style Bold Italic" [en]
  • BookmanOldStyle-Bold "Bookman Old Style Bold" [en]

Brush Script MT

  • BrushScriptMT "Brush Script MT Italic" [en]

Chalkboard

  • Chalkboard "Chalkboard" [en]
  • Chalkboard-Bold "Chalkboard Bold" [en]

Comic Sans MS

  • ComicSansMS "Comic Sans MS" [en]
  • ComicSansMS-Bold "Comic Sans MS Bold" [en]

Corsiva Hebrew

  • CorsivaHebrew "Corsiva Hebrew" [en]
  • CorsivaHebrew-Bold "Corsiva Hebrew Bold" [en]

DecoType Naskh

  • DecoTypeNaskh "DecoType Naskh" [en]

Devanagari MT

  • DevanagariMT "Devanagari MT" [en]
  • DevanagariMT-Bold "Devanagari MT Bold" [en]

BiauKai

  • DFKaiShu-SB-Estd-BF "BiauKai" [zh-Hant]

Wawati SC

  • DFWaWaSC-W5 "Wawati SC Regular" [zh-Hans]

Wawati TC

  • DFWaWaTC-W5 "Wawati TC Regular" [zh-Hant]

Dijla

  • Dijla "Dijla" [ar]

Diwan Kufi

  • DiwanKufi "Diwan Kufi" [en]

Diwan Thuluth

  • DiwanThuluth "Diwan Thuluth" [en]

Farisi

  • Farisi "Farisi" [en]

Lantinghei SC

  • FZLTZHK--GBK1-0 "Lantinghei SC Demibold" [zh-Hans]
  • FZLTXHK--GBK1-0 "Lantinghei SC Extralight" [zh-Hans]
  • FZLTTHK--GBK1-0 "Lantinghei SC Heavy" [zh-Hans]

Lantinghei TC

  • FZLTZHB--B51-0 "Lantinghei TC Demibold" [zh-Hant]
  • FZLTXHB--B51-0 "Lantinghei TC Extralight" [zh-Hant]
  • FZLTTHB--B51-0 "Lantinghei TC Heavy" [zh-Hant]

Garamond

  • Garamond "Garamond " [en]
  • Garamond-Italic "Garamond Italic" [en]
  • Garamond-BoldItalic "Garamond Bold Italic" [en]
  • Garamond-Bold "Garamond Bold" [en]

Gujarati MT

  • GujaratiMT "Gujarati MT" [en]
  • GujaratiMT-Bold "Gujarati MT Bold" [en]

Gurmukhi MN

  • GurmukhiMN "Gurmukhi MN" [en]
  • GurmukhiMN-Bold "Gurmukhi MN Bold" [en]

Gurmukhi Sangam MN

  • GurmukhiSangamMN "Gurmukhi Sangam MN" [en]
  • GurmukhiSangamMN-Bold "Gurmukhi Sangam MN Bold" [en]

Hannotate SC

  • HannotateSC-W5 "Hannotate SC Regular" [zh-Hans]

Hannotate TC

  • HannotateTC-W5 "Hannotate TC Regular" [zh-Hant]
  • HannotateSC-W7 "Hannotate SC Bold" [zh-Hans]
  • HannotateTC-W7 "Hannotate TC Bold" [zh-Hant]

HanziPen SC

  • HanziPenSC-W3 "HanziPen SC Regular" [zh-Hans]

HanziPen TC

  • HanziPenTC-W3 "HanziPen TC Regular" [zh-Hant]
  • HanziPenSC-W5 "Weibei SC Bold" [zh-Hans]
  • HanziPenTC-W5 "HanziPen TC Bold" [zh-Hant]

Herculanum

  • Herculanum "Herculanum" [en]

Hiragino Sans GB

  • HiraginoSansGB-W3 "ヒラギノ角ゴ 簡体中文 W3" [zh-Hans]
  • HiraginoSansGB-W6 "ヒラギノ角ゴ 簡体中文 W6" [zh-Hans]

Hiragino Kaku Gothic Pro

  • HiraKakuPro-W3 "ヒラギノ角ゴ Pro W3" [ja]
  • HiraKakuPro-W6 "ヒラギノ角ゴ Pro W6" [ja]

Hiragino Kaku Gothic Std

  • HiraKakuStd-W8 "ヒラギノ角ゴ Std W8" [ja]

Hiragino Kaku Gothic StdN

  • HiraKakuStdN-W8 "ヒラギノ角ゴ StdN W8" [ja]

Hiragino Maru Gothic Pro

  • HiraMaruPro-W4 "ヒラギノ丸ゴ Pro W4" [ja]

Hiragino Maru Gothic ProN

  • HiraMaruProN-W4 "ヒラギノ丸ゴ ProN W4" [ja]

Hiragino Mincho Pro

  • HiraMinPro-W3 "ヒラギノ明朝 Pro W3" [ja]
  • HiraMinPro-W6 "ヒラギノ明朝 Pro W6" [ja]

YuMincho

  • YuMin-Medium "游明朝体 ミディアム" [ja]
  • YuMin-Demibold "游明朝体 デミボールド" [ja]

YuGothic

  • YuGo-Bold "游ゴシック体 ボールド" [ja]
  • YuGo-Medium "游ゴシック体 ミディアム" [ja]

Hoefler Text

  • HoeflerText-Ornaments "Hoefler Text Ornaments" [en]

Impact

  • Impact "Impact" [en]

InaiMathi

  • InaiMathi "InaiMathi" [en]

Iowan Old Style Black

  • IowanOldStyle-Black "Iowan Old Style Black" [en]
  • IowanOldStyle-BlackItalic "Iowan Old Style Black Italic" [en]

Iowan Old Style

  • IowanOldStyle-Bold "Iowan Old Style Bold" [en]
  • IowanOldStyle-BoldItalic "Iowan Old Style Bold Italic" [en]
  • IowanOldStyle-Italic "Iowan Old Style Italic" [en]
  • IowanOldStyle-Roman "Iowan Old Style Roman" [en]
  • IowanOldStyle-Titling "Iowan Old Style Titling" [en]

PilGi

  • JCfg "PilGi Regular" [ko]

HeadLineA

  • JCHEadA "HeadLineA Regular" [ko]

GungSeo

  • JCkg "GungSeo Regular" [en]

PCMyungjo

  • JCsmPC "PCMyungjo Regular" [ko]

Kannada MN

  • KannadaMN "Kannada MN" [en]
  • KannadaMN-Bold "Kannada MN Bold" [en]

Kefa

  • Kefa-Regular "Kefa Regular" [en]
  • Kefa-Bold "Kefa Bold" [en]

Khmer MN

  • KhmerMN "Khmer MN" [en]
  • KhmerMN-Bold "Khmer MN Bold" [en]

Khmer Sangam MN

  • KhmerSangamMN "Khmer Sangam MN" [en]

Kokonor

  • Kokonor "Kokonor Regular" [en]

Koufi Abjadi

  • KoufiAbjadi "Koufi Abjadi" [ar]

Krungthep

  • Krungthep "Krungthep" [en]

KufiStandardGK

  • KufiStandardGK "KufiStandardGK" [en]

Laimoon

  • Laimoon "Laimoon" [ar]

Lao MN

  • LaoMN "Lao MN" [en]
  • LaoMN-Bold "Lao MN Bold" [en]

Lao Sangam MN

  • LaoSangamMN "Lao Sangam MN" [en]

Apple LiGothic

  • LiGothicMed "Apple LiGothic Medium" [zh-Hant]

LiHei Pro

  • LiHeiPro "LiHei Pro" [zh-Hant]

LiSong Pro

  • LiSongPro "LiSong Pro" [zh-Hant]

Apple LiSung

  • LiSungLight "Apple LiSung Light" [zh-Hant]

Lucida Grande

  • LucidaGrande "Lucida Grande" [en]
  • LucidaGrande-Bold "Lucida Grande Bold" [en]

.Lucida Grande UI

  • .LucidaGrandeUI ".Lucida Grande UI" [en]
  • .LucidaGrandeUI-Bold ".Lucida Grande UI Bold" [en]

Malayalam MN

  • MalayalamMN "Malayalam MN" [en]
  • MalayalamMN-Bold "Malayalam MN Bold" [en]

Microsoft Sans Serif

  • MicrosoftSansSerif "Microsoft Sans Serif" [en]

Gurmukhi MT

  • MonotypeGurmukhi "Gurmukhi MT" [en]

Mshtakan

  • Mshtakan "Mshtakan" [en]
  • MshtakanBold "Mshtakan Bold" [en]
  • MshtakanBoldOblique "Mshtakan BoldOblique" [en]
  • MshtakanOblique "Mshtakan Oblique" [en]

Muna

  • Muna "Muna" [en]
  • MunaBold "Muna Bold" [en]
  • MunaBlack "Muna Black" [en]

Myanmar MN

  • MyanmarMN "Myanmar MN" [en]
  • MyanmarMN-Bold "Myanmar MN Bold" [en]

Myanmar Sangam MN

  • MyanmarSangamMN "Myanmar Sangam MN" [en]

Nadeem

  • Nadeem "Nadeem" [en]

Nanum Brush Script

  • NanumBrush "Nanum Brush Script" [ko]

Nanum Pen Script

  • NanumPen "Nanum Pen Script" [ko]

NanumGothic

  • NanumGothic "NanumGothic" [ko]
  • NanumGothicBold "NanumGothic Bold" [ko]
  • NanumGothicExtraBold "NanumGothic ExtraBold" [ko]

NanumMyeongjo

  • NanumMyeongjo "NanumMyeongjo" [ko]
  • NanumMyeongjoBold "NanumMyeongjoBold" [ko]
  • NanumMyeongjoExtraBold "NanumMyeongjoExtraBold" [ko]

New Peninim MT

  • NewPeninimMT "New Peninim MT" [en]
  • NewPeninimMT-Bold "New Peninim MT Bold" [en]
  • NewPeninimMT-BoldInclined "New Peninim MT Bold Inclined" [en]
  • NewPeninimMT-Inclined "New Peninim MT Inclined" [en]

Nisan

  • Nisan "Nisan" [ar]

Oriya MN

  • OriyaMN "Oriya MN" [en]
  • OriyaMN-Bold "Oriya MN Bold" [en]

Osaka

  • Osaka "Osaka" [ja]
  • Osaka-Mono "Osaka−等幅" [ja]

Plantagenet Cherokee

  • PlantagenetCherokee "Plantagenet Cherokee" [en]

PT Sans

  • PTSans-Regular "PT Sans" [en]
  • PTSans-Italic "PT Sans Italic" [en]
  • PTSans-Bold "PT Sans Bold" [en]
  • PTSans-BoldItalic "PT Sans Bold Italic" [en]

PT Sans Caption

  • PTSans-Caption "PT Sans Caption" [en]
  • PTSans-CaptionBold "PT Sans Caption Bold" [en]

PT Sans Narrow

  • PTSans-Narrow "PT Sans Narrow" [en]
  • PTSans-NarrowBold "PT Sans Narrow Bold" [en]

Raanana

  • Raanana "Raanana" [en]
  • RaananaBold "Raanana Bold" [en]

Raya

  • Raya "Raya" [ar]

Sana

  • Sana "Sana" [en]

Sathu

  • Sathu "Sathu" [en]

Savoye LET

  • SavoyeLetPlain "Savoye LET Plain:1.0" [en]

.Savoye LET CC.

  • .SavoyeLetPlainCC "Savoye LET Plain CC.:1.0" [en]

Hei

  • SIL-Hei-Med-Jian "Hei Regular" [zh-Hans]

Kai

  • SIL-Kai-Reg-Jian "Kai Regular" [zh-Hans]

Silom

  • Silom "Silom" [en]

Sinhala MN

  • SinhalaMN "Sinhala MN" [en]
  • SinhalaMN-Bold "Sinhala MN Bold" [en]

Somer

  • Somer "Somer" [ar]

Baoli SC

  • STBaoli-SC-Regular "Baoli SC Regular" [zh-Hans]

STFangsong

  • STFangsong "STFangsong" [zh-Hans]

STHeiti

  • STHeiti "STHeiti" [zh-Hans]

STIXGeneral

  • STIXGeneral-Bold "STIXGeneral-Bold" [en]
  • STIXGeneral-BoldItalic "STIXGeneral-BoldItalic" [en]
  • STIXGeneral-Italic "STIXGeneral-Italic" [en]
  • STIXGeneral-Regular "STIXGeneral-Regular" [en]

STIXIntegralsD

  • STIXIntegralsD-Bold "STIXIntegralsD-Bold" [en]
  • STIXIntegralsD-Regular "STIXIntegralsD-Regular" [en]

STIXIntegralsSm

  • STIXIntegralsSm-Bold "STIXIntegralsSm-Bold" [en]
  • STIXIntegralsSm-Regular "STIXIntegralsSm-Regular" [en]

STIXIntegralsUp

  • STIXIntegralsUp-Bold "STIXIntegralsUp-Bold" [en]
  • STIXIntegralsUp-Regular "STIXIntegralsUp-Regular" [en]

STIXIntegralsUpD

  • STIXIntegralsUpD-Bold "STIXIntegralsUpD-Bold" [en]
  • STIXIntegralsUpD-Regular "STIXIntegralsUpD-Regular" [en]

STIXIntegralsUpSm

  • STIXIntegralsUpSm-Bold "STIXIntegralsUpSm-Bold" [en]
  • STIXIntegralsUpSm-Regular "STIXIntegralsUpSm-Regular" [en]

STIXNonUnicode

  • STIXNonUnicode-Bold "STIXNonUnicode-Bold" [en]
  • STIXNonUnicode-BoldItalic "STIXNonUnicode-BoldItalic" [en]
  • STIXNonUnicode-Italic "STIXNonUnicode-Italic" [en]
  • STIXNonUnicode-Regular "STIXNonUnicode-Regular" [en]

STIXSizeFiveSym

  • STIXSizeFiveSym-Regular "STIXSizeFiveSym-Regular" [en]

STIXSizeFourSym

  • STIXSizeFourSym-Bold "STIXSizeFourSym-Bold" [en]
  • STIXSizeFourSym-Regular "STIXSizeFourSym-Regular" [en]

STIXSizeOneSym

  • STIXSizeOneSym-Bold "STIXSizeOneSym-Bold" [en]
  • STIXSizeOneSym-Regular "STIXSizeOneSym-Regular" [en]

STIXSizeThreeSym

  • STIXSizeThreeSym-Bold "STIXSizeThreeSym-Bold" [en]
  • STIXSizeThreeSym-Regular "STIXSizeThreeSym-Regular" [en]

STIXSizeTwoSym

  • STIXSizeTwoSym-Bold "STIXSizeTwoSym-Bold" [en]
  • STIXSizeTwoSym-Regular "STIXSizeTwoSym-Regular" [en]

STIXVariants

  • STIXVariants-Bold "STIXVariants-Bold" [en]
  • STIXVariants-Regular "STIXVariants-Regular" [en]

Kaiti SC

  • STKaiti-SC-Black "Kaiti SC Black" [zh-Hans]
  • STKaiti-SC-Bold "Kaiti SC Bold" [zh-Hans]

Kaiti TC

  • STKaiTi-TC-Bold "Kaiti TC Bold" [zh-Hant]
  • STKaiti-SC-Regular "Kaiti SC Regular" [zh-Hans]
  • STKaiTi-TC-Regular "Kaiti TC Regular" [zh-Hant]

Libian SC

  • STLibian-SC-Regular "Libian SC Regular" [zh-Hans]

Songti SC

  • STSongti-SC-Black "Songti SC Black" [zh-Hans]
  • STSongti-SC-Light "Songti SC Light" [zh-Hans]

Songti TC

  • STSongti-TC-Light "Songti SC Light" [zh-Hant]
  • STSongti-SC-Bold "Songti SC Bold" [zh-Hans]
  • STSongti-TC-Bold "Songti TC Bold" [zh-Hant]
  • STSongti-SC-Regular "Songti SC Regular" [zh-Hans]
  • STSongti-TC-Regular "Songti TC Regular" [zh-Hant]
  • STXihei "STXihei" [zh-Hans]

Xingkai SC

  • STXingkai-SC-Bold "Xingkai SC Bold" [zh-Hans]
  • STXingkai-SC-Light "Xingkai SC Light" [zh-Hans]

Yuanti SC

  • STYuanti-SC-Bold "Yuanti SC Bold" [zh-Hans]
  • STYuanti-SC-Light "Yuanti SC Light" [zh-Hans]
  • STYuanti-SC-Regular "Yuanti SC Regular" [zh-Hans]

Tahoma

  • Tahoma "Tahoma" [en]
  • Tahoma-Bold "Tahoma Bold" [en]

Tamil MN

  • TamilMN "Tamil MN" [en]
  • TamilMN-Bold "Tamil MN Bold" [en]

Telugu MN

  • TeluguMN "Telugu MN" [en]
  • TeluguMN-Bold "Telugu MN Bold" [en]

Waseem

  • Waseem "Waseem" [en]
  • WaseemLight "Waseem Light" [en]

Webdings

  • Webdings "Webdings" [en]

Weibei SC

  • Weibei-SC-Bold "Weibei SC Bold" [zh-Hans]

Weibei TC

  • Weibei-TC-Bold "Weibei TC Bold" [zh-Hant]

Wingdings

  • Wingdings-Regular "Wingdings" [en]

Wingdings 2

  • Wingdings2 "Wingdings 2" [en]

Wingdings 3

  • Wingdings3 "Wingdings 3" [en]

Yaziji

  • Yaziji "Yaziji" [ar]

Yuppy SC

  • YuppySC-Regular "Yuppy SC Regular" [zh-Hans]

Yuppy TC

  • YuppyTC-Regular "Yuppy TC Regular" [zh-Hant]

Zawra

  • Zawra-Bold "Zawra Bold" [ar]
  • Zawra-Heavy "Zawra Heavy" [ar]

Century Gothic

  • CenturyGothic "Century Gothic" [en]
  • CenturyGothic-Bold "Century Gothic Bold" [en]
  • CenturyGothic-Italic "Century Gothic Italic" [en]
  • CenturyGothic-BoldItalic "Century Gothic Bold Italic" [en]

Century Schoolbook

  • CenturySchoolbook "Century Schoolbook" [en]
  • CenturySchoolbook-Bold "Century Schoolbook Bold" [en]
  • CenturySchoolbook-Italic "Century Schoolbook Italic" [en]
  • CenturySchoolbook-BoldItalic "Century Schoolbook Bold Italic" [en]

Tw Cen MT

  • TwCenMT-Regular "Tw Cen MT" [en]
  • TwCenMT-Bold "Tw Cen MT Bold" [en]
  • TwCenMT-Italic "Tw Cen MT Italic" [en]
  • TwCenMT-BoldItalic "Tw Cen MT Bold Italic" [en]

iOS 6 or 7で「游ゴシック体」や「ヒラギノ丸ゴシック」を使う

20131027032814


OS XではMarvericks (10.9) から「游ゴシック体」と「游明朝体」が標準搭載されたことで話題になりましたが、実はiOSでも6以上からこれらのフォントが使用できます。


iOS 6から追加ダウンロードフォントという仕組みが導入され、游ゴシック体などのフォントはその追加ダウンロードフォントという形で提供されています。
アプリケーションからは必要に応じてフォントをダウンロードして利用します。

フォント一覧

iOS 6で使えるフォントはアップルの下記のページに記載されています。
このページの下の方に追加情報として記載されているフォントがダウンロードフォントになります。
iOS 6:フォントリスト (http://support.apple.com/kb/HT5484?viewlocale=ja_JP&locale=ja_JP)


上記のページに記載されている追加ダウンロードフォントのうち、日本語のフォントは以下の8つです。

  • Hiragino Kaku Gothic Std W8 (ヒラギノ角ゴ Std W8)
  • Hiragino Kaku Gothic StdN W8 (ヒラギノ角ゴ StdN W8)
  • Hiragino Maru Gothic Pro W4 (ヒラギノ丸ゴ Pro W4)
  • Hiragino Maru Gothic ProN W4 (ヒラギノ丸ゴ ProN W4)
  • YuGothic Bold (游ゴシック体 ボールド)
  • YuGothic Medium (游ゴシック体 ミディアム)
  • YuMincho Demibold (游明朝体 デミボールド)
  • YuMincho Medium (游明朝体 ミディアム)


このうちヒラギノに関してはProNとPro、StdNとStdの違いで2書体になってるので実質6書体ですね。
ヒラギノ丸ゴシックやヒラギノ角ゴシックのW8などは標準搭載のフォントとはかなり趣きが異なるので使いどころによって重宝します。


iOS 7ではさらに少しフォントが追加されているのですがそれについては別の記事で記載します。

APIについて

追加ダウンロードフォントを利用するには、必要に応じてダウンロードの処理をします。
ダウンロードには`CoreText.framework`の下記のAPIを利用します。

bool CTFontDescriptorMatchFontDescriptorsWithProgressHandler(
    CFArrayRef                          descriptors,
    CFSetRef                            mandatoryAttributes,
    CTFontDescriptorProgressHandler     progressBlock) CT_AVAILABLE(10_9, 6_0);

このAPIはドキュメントに解説が載っていないので、利用方法はヘッダファイルを見ます。


また、アップルから公式のサンプルコードが提供されていますのでこちらも参考になります。
DownloadFont - iOS Developer Library (https://developer.apple.com/LIBRARY/IOS/samplecode/DownloadFont/Introduction/Intro.html)


情報は少ないですが、特に難しいことはなく、1番目の引数にFont Descriptorの形でフォント情報を渡すと、適宜ブロックのコールバックが呼ばれるという仕組みです。


追加ダウンロードフォントについては、フォント名がわかっているのでフォント名からFont Descriptorを作ります。

NSDictionary *attributes = @{(id)kCTFontNameAttribute: fontName};
CTFontDescriptorRef fontDescriptor = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)attributes);
NSArray *fontDescriptors = @[(__bridge id)fontDescriptor];
CFRelease(fontDescriptor);


そして、Font Descriptorを先ほどのAPIに渡せば、必要に応じてフォントをダウンロードし、アプリケーションで利用できるように登録するところまでやってくれます。
ダウンロードの進捗やトータルのサイズなどの情報はブロックの引数の`progressParameter`に入っています。
アプリケーションでダウンロード中のプログレスバーなどを表示するにはこのパラメータの値を利用します。


ダウンロードされたフォントはアプリケーションごとに保存されるわけではなく、すべてのアプリケーションで共有の場所に保存されます。
例えば下記の場所です。

file:///private/var/mobile/Library/Assets/com_apple_MobileAsset_Font/83f5ce0efa7a810b73a7231c0e107f2955f2c85c.asset/AssetData/Yu%20Gothic%20Bold.otf


つまり、あるアプリケーションでフォントがダウンロードされていれば(例えばiBooksなど)、別のアプリケーションではダウンロード処理はスキップしてそのフォントを利用できます。
ただ、一度ダウンロードされたフォントでもデバイスの空き容量によって削除されることがあるので、プログラムはそれを考慮して作成する必要があります。
また、すでに他のアプリケーションでフォントがダウンロードされていても、自分のアプリケーションで利用可能にするには上記のAPIを呼ぶ必要があります。
さらに、どのアプリケーションでダウンロードしたかにかかわらず、一度アプリケーションを終了すると利用登録が解除されてしまうので、次に起動したときには再度上記のAPIを呼ぶ必要があります。


まとめると、

  • 追加ダウンロードフォントはデバイス全体で共有される。
  • ダウンロードされたフォントは、自動的にシステムから削除されることがある。
  • フォントがダウンロード済みならダウンロード処理は自動的にスキップされる。
  • フォントが他のアプリケーションによってダウンロード済みであっても、自分のアプリケーションで利用可能にするにはこのAPIを呼ぶ必要がある。
  • 自分のアプリケーションでダウンロードしたフォントであっても、アプリケーションを終了したら利用登録が解除されるので、再度利用可能な状態にするにはこのAPIを呼ぶ必要がある。


ややこしいように感じますが、ダウンロードのAPIは必要ならダウンロードされ、ダウンロード済みならダウンロードはスキップされてロード処理だけをする、という動きをするので、要するに最初に利用しようとしたときにダウンロードのAPIを呼べばいいということになります。
ただ、単純にダウンロードのAPIを毎回呼ぶとすると、ダウンロードしたくないときにもダウンロードされてしまうので、必要に応じてダウンロードはキャンセルできるようにしたほうがいいでしょう。

フォントのダウンロードおよび利用登録

追加ダウンロードフォントを利用可能にする処理は下記のようになります。
ダウンロードの処理はフォントがどのアプリケーションによってもダウンロードされてないときのみ行われます。
フォントがダウンロード済みであっても、アプリケーションを起動しただけの状態では利用可能になっていないので、この処理をする必要があります。その場合、コールバックはダウンロードのステータスになることはなく、短時間で`kCTFontDescriptorMatchingDidFinish`の状態になります。

CTFontDescriptorMatchFontDescriptorsWithProgressHandler((__bridge CFArrayRef)fontDescriptors, NULL, ^bool(CTFontDescriptorMatchingState state, CFDictionaryRef progressParameter) {
    NSDictionary *parameter = (__bridge NSDictionary *)progressParameter;
    double progressValue = [parameter[(id)kCTFontDescriptorMatchingPercentage] doubleValue];
    
    if (state == kCTFontDescriptorMatchingDidBegin) { // 処理の開始に1度だけ呼ばれる
        dispatch_async( dispatch_get_main_queue(), ^ {
            // ダウンロードはサブスレッドで行われるのでUIの更新などはメインスレッドで行う
            ...
        });
    } else if (state == kCTFontDescriptorMatchingDidFinish) { // 処理の終了時に1度だけ呼ばれる
        dispatch_async( dispatch_get_main_queue(), ^ {
            UIFont *font = [UIFont fontWithName:fontName size:1.0f];
            // この時点でフォントが利用可能になる
            ...
        });
    } else if (state == kCTFontDescriptorMatchingWillBeginDownloading) {
        // フォントが未ダウンロードの場合のみ、ダウンロードの開始前に呼ばれる
        ...
    } else if (state == kCTFontDescriptorMatchingDownloading) {
        // ダウンロード中、ダウンロードの進捗によって適宜呼ばれる
        ...
    } else if (state == kCTFontDescriptorMatchingDidFinishDownloading) {
        // ダウンロード完了時に呼ばれる
        ...
    } else if (state == kCTFontDescriptorMatchingDidFailWithError) {
        // ダウンロードが失敗したときに呼ばれる
        ...
    }
    
    return (bool)YES;
});


ダウンロード済みのフォントがある場合にフォントのロードだけ行い、ダウンロードはしたくないという場合は、例えば下記のようにします。

CTFontDescriptorMatchFontDescriptorsWithProgressHandler((__bridge CFArrayRef)fontDescriptors, NULL, ^bool(CTFontDescriptorMatchingState state, CFDictionaryRef progressParameter) {
    if (state == kCTFontDescriptorMatchingDidFinish) {
        dispatch_async( dispatch_get_main_queue(), ^ {
            UIFont *font = [UIFont fontWithName:fontName size:1.0f];
            if (font) {
                if ([self.delegate respondsToSelector:@selector(fontDownloaderDidFinish:fontName:)]) {
                    [self.delegate fontDownloaderDidFinish:self fontName:fontName];
                }
            }
        });
    } else if (state == kCTFontDescriptorMatchingWillBeginDownloading) {
        return (bool)NO;
    }
    
    return (bool)YES;
});


`kCTFontDescriptorMatchingWillBeginDownloading`で`NO`を返しているのでそれ以上の処理は行われません。
もしダウンロード中のキャンセルをサポートする場合にも同様にすることで実現できます。


アップルのサンプルコードは主に中国語フォントを使っているので、日本語フォントについて簡単に試せるようにしたコードをGithubで公開しているので、よかったらご覧ください。

kishikawakatsumi/DownloadFont · GitHub

チュートリアルなどでUIPageControlを使うときは標準のアクションに対応するのを忘れずに



UIPageControlはiPhoneのホーム画面でも使われている、今何ページ目かを示すUIControlのサブクラスです。
最初のiOSからあって、特徴的なUIなのでフリックでページをめくる画面ではこれを使って現在のページを示すのが定番になっています。
特に最近では初回起動時のチュートリアル画面でよく使われます。


ただ、意外と経験のあるひとが書いたものでも、このコンポーネントがタップによって値が変わるコントロールであることを忘れているのをけっこう見ます。

これを忘れると、UIPageControlのドットのところをタップすると、ドットの場所は変わるのに画面は変わらないので、ちょっとマヌケな感じになってしまいます。


UIPageControlはUISliderなどと同様にUIControlのサブクラスなので、基本的にユーザーの操作によって値が変わるコントロールです。
見た目に特徴があるので、つい装飾のためだけのコンポーネントだと思ってしまいますが、タップによってページを切り替えることを想定されています。


↓ 下記はアップルが提供しているUIPageControlの使い方を示すサンプルコードです。
UISliderなどと同様に、タップで値が変わったときはUIControlEventValueChangedのイベントが発生するので、そのイベントに対応したアクションで処理をします(このサンプルコードでは`changePage:`)。


PageControl

- (void)gotoPage:(BOOL)animated
{
    NSInteger page = self.pageControl.currentPage;
 
    // load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
    [self loadScrollViewWithPage:page - 1];
    [self loadScrollViewWithPage:page];
    [self loadScrollViewWithPage:page + 1];
    
    // update the scroll view to the appropriate page
    CGRect bounds = self.scrollView.bounds;
    bounds.origin.x = CGRectGetWidth(bounds) * page;
    bounds.origin.y = 0;
    [self.scrollView scrollRectToVisible:bounds animated:animated];
}
 
- (IBAction)changePage:(id)sender
{
    [self gotoPage:YES];    // YES = animate
}


ただ、実際はUIPageControlのドットのところがタップできることを知らないひとも多いので、チュートリアルなどで単に装飾のために使ってるのであれば、`pageControl.userInteractionEnabled = NO;`などとして、タップに反応しないようにしてしまうのも手かと思います。

CoreTextを使って簡単に画像付きリッチテキストを表示できるSECoreTextViewに編集機能がつきました。

kishikawakatsumi/SECoreTextView · GitHub
iOS/Macの両方で使えて、文字の選択やリンクのクリックに対応したテキストビューをテスト公開しました。 - 24/7 twenty-four seven



前に書いたSECoreTextViewに編集機能を実装しました (iOSのみ)。


SECoreTextViewはCoreTextを使って簡単にクリッカブルなリンクや画像付きのリッチテキストを表示できるテキストビューの代替実装としてのライブラリです。


以前のものはそこそこ簡単に豊かな表現ができるのでこれはこれでけっこう実用的だったと思います。
↓ このように画像を含めたテキストを表示したり、リンクはクリックに反応して任意の処理をすることができます。
画像に限らず、画面に表示できるものはボタンでもその他のビューでもブロックを渡して任意の描画をすることも可能です。


iOS ScreenShot 1 iOS ScreenShot 1


そんな感じで、表示のみなら標準のUITextViewやUIWebViewをがんばって使うよりは柔軟で取り回しやすいのでけっこう便利に使っていたのですが、だんだん表示だけでは物足りなくなってきたので編集できるようにしてみました。

iOS ScreenShot 1 20130927032539

これまでの実装に加えて、UITextInput/UIKeyInput Protocol を実装して、標準のテキストビューと同じようにキーボードや日本語変換システムの入力を処理しています。


たいていの画面に表示できるものは扱えるので、わりと万能なテキスト編集コンポーネントになったんじゃないかなとおもいます。
だいたいの動きがわかるムービーを作ったのでこちらもどうぞ。
SECoreTextView Demo on Vimeo


UITextInputの実装やCoreTextはいろいろおもしろかったので、技術的なところはまた機会をみて書こうと思います。
今後はパフォーマンス・チューニングやOS Xのほうの実装などを予定しています。あ、それと音声入力への対応ですね。


メッセージアプリやブログエディタなどに、応用がきいて使いやすいと思いますので、いろいろ実戦投入していただけるとうれしいです。
今のうちなら何かあったらけっこうすぐに私が対応できると思います。よろしくお願いします。

第4回iphone_dev_jp 東京iPhone/Mac勉強会を開催しました

第4回 iphone_dev_jp 東京iPhone/Mac勉強会 : ATND


しばらく休んでいたのですが久しぶりにいつもおなじみのVOYAGE GROUPさんの会場をお借りして開催いたしました。


今回は幸運なことにEvernote本社からMac版EvernoteのUIのリニューアルの指揮をされましたJack Hirschさんにきていただき、背景や開発手法などをお話していただきました。

大勢のかたにきていただき、質問が飛び交う活発な会することができてよかったと思います。

Jack Hirschさんおよび、Evernoteのかたがたも参加者の熱意に感銘を受けたと言っていただけました。


通常の発表においても、興味深い内容の話ばかりで、参加していただいたかたには満足いただけたのではないでしょうか。


ホストしていただいたVOYAGE GROUPの @lesamoureuses さん、@huin さんはじめ、手伝っていただいたかた、発表してくださったかた、参加してくださったかた、どうもありがとうございました。


今度はまたハッカソンやりたいね、という話を聞いたので次はハッカソンかなーと思っています。
来月のiOS 7の発表次第でUIコンポーネントはいろいろ必要なものが出てくると思うので、iOS7時代のUIコンポーネントを書く、みたいなのがいいかなと思っています。


また、今回発表してくださった id:ninjinkun の所属する株式会社はてなでは毎年恒例のサマーインターンの募集が始まっています。


はてなのインターンシッププログラムは非常にしっかりした内容でレベルが高いことで有名で、はてなのインターンに来た学生が最終的に他の企業からひっぱりだこになってしまって困っているみたいな話もよく聞きます。

id:ninjinkun によると今回はサーバーサイドだけでなくiPhoneアプリケーションのカリキュラムもあるということなので、両方をバランスよく経験できると貴重な機会だと思います。

参加資格のある学生のかたはぜひ応募してみると良いと思います。

株式会社はてな

発表内容と資料のまとめ

EasyStyleGKさん Objective-C atomicity
_ishkawaさん iOS5で動くUIRefreshControlの作り方
cocoponさん フラットデザインの話
novi_さん クライアント系iOSアプリのつくりかた(実装と開発プロセスの話)
Jack Hirschさん Evernote のユーザエクスペリエンスアプローチ
ninjinkunさん NJKWebViewProgressについて
kishikawa katsumi Mac/iOSのリッチテキストの表示について


↓ Slidrsで共有した資料
第4回 iphone_dev_jp 東京iPhone/Mac勉強会 - Slidrs

iPhone/iPadで画像をクロッピングするライブラリを公開しました

kishikawakatsumi/PEPhotoCropEditor · GitHub

PEPhotoCropEditor は iPhone/iPad アプリに画像をクロッピングする機能を簡単に追加します。
UIは標準のPhotos.appに似ていておもしろい動きをします。

ScreenShot 1 [Movie 1

インストール

CocoaPodsでインストールできます。

pod 'PEPhotoCropEditor'

または、Lib/ ディレクトリと Resources/ ディレクトリのファイルをすべてプロジェクトにコピーして、下記のフレームワークをリンクしてください。

  • QuartzCore.framework
  • AVFoundation.framework

使い方

ビューコントローラを使う場合

(UINavigationController を使わなくても動きます。)

PECropViewController *controller = [[PECropViewController alloc] init];
controller.delegate = self;
controller.image = self.imageView.image;

UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller];
[self presentViewController:navigationController animated:YES completion:NULL];
ビューを直接使う場合
self.cropView = [[PECropView alloc] initWithFrame:contentView.bounds];
[self.view addSubview:self.cropView];
Cropping された画像を取り出す
デリゲートメソッドから
- (void)cropViewController:(PECropViewController *)controller didFinishCroppingImage:(UIImage *)croppedImage
{
    [controller dismissViewControllerAnimated:YES completion:NULL];
    self.imageView.image = croppedImage;
}
ビューのプロパティから
UIImage *croppedImage = self.cropView.croppedImage;

iOS/Macの両方で使えて、文字の選択やリンクのクリックに対応したテキストビューをテスト公開しました。

kishikawakatsumi/SECoreTextView · GitHub

iOS ScreenShot 1


OS X ScreenShot 1


SECoreTextView はリッチテキストの表示と文字の選択(現在はOS Xのみ)やリンクがクリック可能だったりするテキストビューです。
別のアプリでテーブルビューのセルにリンクを含むテキストを表示するのに、既存のものでMacで使えるいい感じのものが今ひとつ見つからなかったので書きました。

OS X で使うだけだとなんなので、せっかくだから iOS にも対応してみました。

UITableVIewやNSTableVIewのセルで使うと便利だと思います。

iOS のほうは半日くらいでちょちょっと書いただけなのでおかしなところが結構あると思うので見つけたら教えてください。

iPhone の画面操作を録画するライブラリを公開しました。

kishikawakatsumi/ScreenRecorder · GitHub

ScreenRecorder は iOS デバイスの画面を連続的にキャプチャして、動画に変換することで画面の操作を録画することができる機能をアプリケーションに追加します。
開発中のソフトウェアのユーザーテストなどに利用すると効果的です。

使い方

1. 以下のファイルをプロジェクトに追加します

  • Lib/SRScreenRecorder.h
  • Lib/SRScreenRecorder.m
  • Vendor/KTouchPointerWindow.h
  • Vendor/KTouchPointerWindow.m

2. 以下のフレームワークをリンクします

  • QuartzCore.framework
  • CoreVideo.framework
  • CoreMedia.framework
  • AVFoundation.framework

startRecording で録画を開始します。
デフォルトの設定は

  • バックグラウンドに入ったときに自動保存
  • 10 分ごとに自動保存、ファイルのローテート
  • 30FPSで録画
  • タッチ箇所の表示

となっています。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[SRScreenRecorder sharedInstance] startRecording];
    return YES;
}


いくつかの挙動は、設定で変更することができます。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    SRScreenRecorder *recorder = [SRScreenRecorder sharedInstance];
    recorder.frameInterval = 1; // 60 FPS
    recorder.autosaveDuration = 1800; // 30 minutes
    recorder.showsTouchPointer = NO; // hidden touch pointer
    recorder.filenameBlock = ^(void) {
        return @"screencast.mov";
    }; // change filename
    
    [recorder startRecording];
    
    return YES;
}


タッチ・ポインターの表示には @ さんの KTouchPointerWindow を利用しています。
iPhone/iPadの画面にタッチ位置を表示するためのソースコード KTouchPointerWindow を公開しました
itok/KTouchPointerWindow · GitHub

リンクするだけで iOS 6 で Google Map が使えるようになる YAMapKit を公開しました。

kishikawakatsumi/YAMapKit · GitHub

YAMapKit は MapKit.framework と(ほぼ)互換性のある代替ライブラリです。
Google Maps Javascript API と UIWebView を利用して iOS 6 で Apple の標準地図の代わりに Google Map を使った表示ができます。
MapKit.framework と(ほぼ)互換性があるのでリンク先を差し替えるだけで動作します(たいていの場合は)。

あまりヘビーな利用には向きませんが、アプリケーションの中でちょっと MapKit を使って地図を表示したりピンを挿したりしているという場合に効果的です。

まだ未サポートの機能がたくさんあるので、手伝ってくれる方や、バグレポート、要望を歓迎します。

使い方

  1. MapKit.framework のリンクを外します。
  2. libMapKit.a をリンクします。
  3. CoreLocation.framework をリンクします。

できないこと

  • ジオコーディング(代わりに 'CLGeocoder' を使ってください)
  • カスタムビューのオーバーレイ表示(組み込みのオーバーレイ (MKPolylineView, MKCircleView など) しか使えません)
  • アノテーションのドラッグ&ドロップ
  • アノテーションのコールアウトを表示したあとで更新する

(たぶん他にもいっぱいあります)

利用例


Objective-C でサブクラスのインスタンスから任意のスーパークラスのメソッドを呼ぶ

サブクラスのインスタンスからポリモーフィズムを無視して任意のスーパークラスのメソッドを呼びます。


↓ 下のように Shape クラスと Shape クラスを継承した Path クラス、および Path クラス を継承した Circle があります。
それぞれのクラスで draw メソッドをオーバーライドしています。

////////////////////////////////////////////////////////////////////////
#pragma mark - Shape
////////////////////////////////////////////////////////////////////////
@interface Shape : NSObject
@end

@implementation Shape

- (void)draw {
    NSLog(@"%@", @"Shape.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Path
////////////////////////////////////////////////////////////////////////
@interface Path : Shape
@end

@implementation Path

- (void)draw {
    NSLog(@"%@", @"Path.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Circle
////////////////////////////////////////////////////////////////////////
@interface Circle : Path
@end

@implementation Circle

- (void)draw {
    NSLog(@"%@", @"Circle.");
}

@end


↓ Circle クラスのインスタンスから draw メソッドを呼び出すと Circle クラスの draw メソッドが実行されて "Circle." と出力されます。
たまに多態性を無視してスーパークラスや、スーパークラスのさらにスーパークラスのメソッドを実行したいということってありますよね。
そういうときは 対象メソッドの IMP (メソッドを参照する関数へのポインタ) を使います。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    Shape *shape = [[Circle alloc] init];
    [shape draw]; // => Circle.
    
    SEL selector = @selector(draw);
    
    void(*pathFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Path instanceMethodForSelector:selector];
    pathFunction(shape, selector); // => Path.
    
    void(*shapeFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Shape instanceMethodForSelector:selector];
    shapeFunction(shape, selector); // => Shape.
}

@end

↑ IMP の定義のままだと ARC が戻り値を retain しようとするので、戻り値が void の関数ポインタにキャストしています。


↓ 上記のコードの出力は下記になります。

2012-10-23 13:15:20.632 Monomorphism[50033:c07] Circle.
2012-10-23 13:15:20.633 Monomorphism[50033:c07] Path.
2012-10-23 13:15:20.633 Monomorphism[50033:c07] Shape.


サブクラスのインスタンスからポリモーフィズムを無視して任意のスーパークラスのメソッドが呼べました。
↓ 試したコードの全体を載せておきます。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Shape
////////////////////////////////////////////////////////////////////////
@interface Shape : NSObject
@end

@implementation Shape

- (void)draw {
    NSLog(@"%@", @"Shape.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Path
////////////////////////////////////////////////////////////////////////
@interface Path : Shape
@end

@implementation Path

- (void)draw {
    NSLog(@"%@", @"Path.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Circle
////////////////////////////////////////////////////////////////////////
@interface Circle : Path
@end

@implementation Circle

- (void)draw {
    NSLog(@"%@", @"Circle.");
}

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    Shape *shape = [[Circle alloc] init];
    [shape draw]; // => Circle.
    
    SEL selector = @selector(draw);
    
    void(*pathFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Path instanceMethodForSelector:selector];
    pathFunction(shape, selector); // => Path.
    
    void(*shapeFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Shape instanceMethodForSelector:selector];
    shapeFunction(shape, selector); // => Shape.
}

@end

iOS 6.0 と iOS 5.x の両方で動作するアプリケーションをビルドする設定

iOS 4.0 と iPhone OS 3.x の両方で動作するアプリケーションをビルドする設定 - 24/7 twenty-four seven

↑ こちらも参考に
iOS 4.0 が登場したくらいのときに上の記事を書いて、仕組みは変わってないのですけど Xcode 4.x 系だと UI が変わってるので現在のやり方をまとめます。

ベース SDK と Deployment Target を設定する

プロジェクトの "Build Settings" で "Base SDK" を "Latest iOS" にします。
前にも書きましたが、ベース SDK は最新を指定したほうがいいです。


プロジェクトの "Info" で "Deployment Target" をサポートする OS の最も低いバージョンにします。
(下の場合は iOS 5.0 以降で動作する。)


今なら、Base SDK 6.0 でビルドして、Deployment Target を 5.0 または 5.1 にするのが効果的でしょうか。2世代サポートということで。

新しい Framework を Weak Link (Optional) に設定する

古い環境には含まれていない Framework をリンクしていると、Dynamic Linker がシンボルのロードに失敗してアプリケーションが起動しません。
その場合は、新しい OS にのみ存在する Framework を Weak Link に設定します(今回は Social.framework)。

↑ リンクの設定はプロジェクトではなくターゲットに対してしか行えないので、ターゲットを選択して "Build Phases" > "Link Binary With Libraries" から設定します。
リンクするフレームワークを設定して、 Required > Optional に変更します。

OS のバージョンごとに処理を分岐する

古い API には存在しないセレクタを呼び出したり、クラスを参照したりするとクラッシュしますので必要に応じて処理を分岐します。

- (void)tweet:(id)sender
{
    Class clazz = NSClassFromString(@"SLComposeViewController");
    if (clazz) {
        SLComposeViewController *controller = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
        [self presentViewController:controller animated:YES completion:nil];
    } else {
        TWTweetComposeViewController *controller = [[TWLandscapeTweetComposeViewController alloc] init];
        [self presentViewController:controller animated:YES completion:nil];
    }
}

Auto Layout に注意

Auto Layout は iOS 6 以降しか使えません。Auto Layout を使用していると iOS 5.x などで起動時に関連のクラスが無くてクラッシュします。StoryBoard を新しく追加したときなどにうっかり Auto Layout のチェックをつけたままにしてしまったりするので注意しましょう。

iOS 6 では Supported interface orientations の順番に注意!

最近の Xcode ではアプリケーションが対応しているデバイスの向きをターゲットの Summary 画面から GUI を用いて設定できるようになりましたが、ここから設定する場合はボタンを押す順番に注意する必要があります。

というのも、この画面で設定した内容は、Info.plist の Supported interface orientations (UISupportedInterfaceOrientations) に反映されるのですが、この項目は Array の値で順番が起動時の状態に影響するからなのです。


上記の画面の状態になるように、ボタンを左から順に押していった場合、Info.plist の UISupportedInterfaceOrientations は下記のようになります。これは新規プロジェクトを作成した場合のデフォルト値です。


今度は同じ状態になるように、ボタンを「右から」順に押していきます。すると Info.plist は下記のようになります。
値の順番が変わっています。


実はこの順番は起動時の画面の向きに関係していて、iOS 6 では一番先頭に指定されている画面の向きで起動することになります。
つまり、前の例では縦画面で起動するのですが、後の例では横向きで起動することになります。


別のキーに Initial interface orientation (UIInterfaceOrientation) というものがあって、こちらを指定すると初期状態を指定できそうですが、試したところ iOS 6 ではどうもこの値は無視されるようです。

iOS 6 ではグループスタイルのテーブルビューの背景色がこっそり非推奨になっている。


iOS 6 では上記のカラーを生成するメソッドがヘッダのコメントでひそかに deprecated になっています。

UIInterface.h

// Group style table view backgrounds can no longer be represented by a simple color.
// If you want to have a background in your own view that looks like the table view background,
// then you should create an empty table view and place it behind your content.
+ (UIColor *)groupTableViewBackgroundColor; // This method will be deprecated during the 6.0 seed program


ドキュメントの記載は変わってないのですが、実際に使ってみると今までは下記のコードでピンストライプのカラーが設定されていましたが、iOS 6 だと真っ黒になってしまいます。

self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];


この背景色を使いたい場合は空のテーブルビューを設定しろということなので、iOS 6 から代替のコードは下記のようになります。

UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
[self.view addSubview:tableView];

はやりのシンボルフォントを iOS で画像として扱える SymbolFontKit を公開しました。

kishikawakatsumi/SymbolFontKit · GitHub

ScreenShot1

↑ シミュレータに表示されている画像やツールバーのボタン、タブバーのアイコンは全てフォントです。

シンボルフォントとは要するにアイコン画像などをフォント形式にしたものです。
Webだと最近のブラウザだとWebフォントが使えるので、利用者の環境にフォントがインストールされていなくても使えるので、最近は解像度非依存ということもあっていろいろな Github や Twitter などいろいろなサイトで利用されています。

シンボルフォントについて詳しくは下記のリンク先などを見てください。
Ligature Symbols 〜ほんとにべんりなフォントのはなし〜
【完全版】Ligature Symbols フォントセットの自作方法 - くらげだらけ
Ligature Symbols

で、フォントなのでベクターデータのためどんな解像度でもキレイに表示されることや、1つのアイコンで色違いや別のサイズを表示することが簡単だったり、はやっているので様々なデザインのシンボルフォントが使いやすいライセンスで入手できるなど、iOS でも利用できたら便利だと思って作りました。

使い方

  1. SFKImage.h/m をプロジェクトにコピーします。
  2. 利用したいフォントファイルをプロジェクトにコピーします。
  3. 上記でコピーしたフォントファイルのファイル名を UIAppFonts をキーにして Info.plist に追加します。

API は UIImage 互換なので UIImage と同様の使い方ができます。(実は現在は imageNamed: 以外のインスタンス化はできません)

SFKImage *image = [SFKImage imageNamed:@"print"];


インスタンス化した SFKImage オブジェクトは UIImage のインスタンスと同様に UIButton や UIImageView、UITabBarItem などに直接設定することができます。

self.imageView1.image = [SFKImage imageNamed:@"share"];

UITabBarItem *calendarTabBarItem = [[UITabBarItem alloc] initWithTitle:@"calendar" image:[SFKImage imageNamed:@"calender"] tag:1];
UITabBarItem *globeTabBarItem = [[UITabBarItem alloc] initWithTitle:@"globe" image:[SFKImage imageNamed:@"globe"] tag:2];
_tabBar.items = @[calendarTabBarItem, globeTabBarItem];


フォントのレンダリングは実際に画面に描画されるときまで遅延されるので、UIImage と違って、1つのインスタンスを途中で色や大きさを変えたりできます。

SFKImage *image = [SFKImage imageNamed:@"compass"];
image.size = CGSizeMake(20, 20);
image.color = [UIColor redColor];
self.imageView6.image = image;
    
image.size = CGSizeMake(40, 40);
image.color = [UIColor yellowColor];
self.imageView7.image = image;
    
image.size = CGSizeMake(80, 80);
image.color = [UIColor blueColor];
self.imageView8.image = image;


UIImage として振る舞うためにちょっと無茶をしているのでそのまま AppStore の審査に通るかどうかは「?」ですが近いうちに適当なアプリを提出して調査したいと思います。


SymbolFontKit は第1回iphone_dev_jp東京 iPhone/Mac Hackathon 〜みんなが幸せになるハッカソン〜の成果物です。