Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Finder Sync Extension で Mac 向け便利ツールを作ろう / iOSDC Japan 2021

Finder Sync Extension で Mac 向け便利ツールを作ろう / iOSDC Japan 2021

iOSDC Japan 2021 Day1 15:50~ Track B レギュラートーク(20分)

プロポーザル:https://fortee.jp/iosdc-japan-2021/proposal/891d13b7-9e95-498f-8adc-0cd099f5c15d
デモアプリ1:https://github.com/Kyome22/ScaleHelper
デモアプリ2:https://github.com/Kyome22/RenameHelper
デモアプリ3:https://github.com/Kyome22/NewCanvas

Kyome (Takuto Nakamura)

September 18, 2021
Tweet

More Decks by Kyome (Takuto Nakamura)

Other Decks in Technology

Transcript

  1. CZ,ZPNF
    J04%$+BQBO
    'JOEFS4ZOD&YUFOTJPOͰ
    .BD޲͚ศརπʔϧΛ࡞Ζ͏

    View full-size slide

  2. ,ZPNFʢ͖ΐΊʣ
    ͓࢓ࣄɿ$ZCP[VϞόΠϧΤϯδχΞ
    4XJGUྺɿ೥
    ಘҙ෼໺ɿNBD04޲͚ৗறܕΞϓϦ
    LZPNF
    ,ZPNF
    ,ZPNFTVLF

    View full-size slide

  3. 'JOEFS4ZOD&YUFOTJPOͱ͸

    View full-size slide

  4. 'JOEFS4ZOD&YUFOTJPO
    ϩʔΧϧͱϦϞʔτͰϑΝΠϧΛಉظ͢ΔΞϓϦͷͨΊʹ༻ҙ͞Εͨػೳ֦ு
    ‣ ྫɿ%SPQ#PY΍(PPHMF%SJWFͷΑ͏ͳΦϯϥΠϯετϨʔδαʔϏε

    View full-size slide

  5. 'JOEFS4ZOD&YUFOTJPO
    ࢦఆͨ͠ϑΥϧμͱͦͷ֊૚ԼͷΞΠςϜͷঢ়ଶΛ؂ࢹͰ͖Δ
    ‣ ϑΝΠϧͷछྨɺ࡞੒೔ɺมߋ೔ͳͲͷ৘ใ؂ࢹ
    ‎ ಉظର৅͔Ͳ͏͔ʗಉظࡁΈ͔Ͳ͏͔ͷ൑அ
    ‣ ࢦఆͨ͠ϑΥϧμΛݱࡏϢʔβ͕ૢ࡞͍ͯ͠Δ͔ͷ؂ࢹ
    ‎ ಉظॲཧͷ࠷దԽ

    View full-size slide

  6. 'JOEFS4ZOD&YUFOTJPO
    'JOEFS্ͰΞΠςϜʹόοδΛ෇༩ͯ͠ɺΞΠςϜͷಉظঢ়ଶΛఏࣔͰ͖Δ
    όοδ

    View full-size slide

  7. 'JOEFS4ZOD&YUFOTJPO
    ϑΝΠϧ΍ϑΥϧμͷ؅ཧλεΫΛ࣮ߦ͢ΔɺΧελϜίϯςΩετϝχϡʔ
    ΛఏࣔͰ͖Δ
    ΧελϜίϯςΩετϝχϡʔͱ͸ʁ
    σεΫτοϓ΍'JOEFS্ͰɺӈΫϦοΫ΍
    $POUSPMʴΫϦοΫΛ͢Δͱग़Δϝχϡʔ

    View full-size slide

  8. 'JOEFS4ZOD&YUFOTJPO
    શϑΝΠϧͷಉظͳͲɺάϩʔόϧͳλεΫΛ࣮ߦ͢ΔΧελϜπʔϧόʔ
    ϘλϯΛ'JOEFSͷπʔϧόʔʹ௥ՃͰ͖Δ
    'JOEFS
    ΧελϜπʔϧόʔϘλϯ

    View full-size slide

  9. 'JOEFS4ZOD&YUFOTJPO
    ͍͍ͩͨ෼͔ͬͨͧʂ

    View full-size slide

  10. Ͱ΋͜ͷࢠɺ

    View full-size slide

  11. ϑΝΠϧΛಉظ͢ΔΞϓϦҎ֎ʹ΋
    ࢓૊ΈΛྲྀ༻Ͱ͖ͦ͏ʜ

    View full-size slide

  12. ࢲͷղऍ
    Ϣʔβ͕ಛఆͷϑΥϧμΛૢ࡞࢝͠Ίͨ͜ͱΛݕ஌Ͱ͖Δ
    ಉظͱؔ܎ͳ͘ɺϑΝΠϧ΍ϑΥϧμͷঢ়ଶʹैͬͯόοδΛ෇༩Ͱ͖Δ
    'JOEFS্Ͱબ୒தͷϑΝΠϧ΍ϑΥϧμʹରͯ͠೚ҙͷλεΫΛ࣮ߦͰ͖Δ
    ΋͸΍ɺબ୒தͷϑΝΠϧ΍ϑΥϧμͱؔ܎ͳ͘ɺ೚ҙͷϧʔνϯλεΫΛ
    ࣗಈԽͨ͠ίϚϯυΛୟ͚Δ
    ‎ ϝχϡʔόʔৗறܕͱ͸ҟͳΔܗͷ
    ɹɹɹɹɹɹৗறܕϢʔςΟϦςΟπʔϧͷ࡞੒ʹ࢖͑Δ!

    View full-size slide

  13. 'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊ

    View full-size slide

  14. 'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢجຊͷΩฤʣ
    ऩ༰ΞϓϦͱ'JOEFS4ZOD&YUFOTJPOͷ̎ͭͷλʔήοτͰߏ੒͞ΕΔ
    ػೳ֦ுͷऩ༰ΞϓϦ 'JOEFS4ZOD&YUFOTJPO
    ʢʹ'JOEFSػೳ֦ுʣ

    View full-size slide

  15. 'JOEFSػೳ֦ு͸ɺΞϓϦͷΠϯετʔϧޙɺγεςϜ؀ڥઃఆ͔Β
    ༗ޮʹ͢Δ͜ͱͰػೳ͢Δ
    γεςϜ؀ڥઃఆ
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢجຊͷΩฤʣ

    View full-size slide

  16. ‎ ऩ༰ΞϓϦ͸ɺػೳ֦ுͷ༗ޮΛଅ͢ಋઢͷ໾ׂΛ୲͏
    ಋઢͷྫ
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢجຊͷΩฤʣ

    View full-size slide

  17. 'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢԼ४උฤʣ
    ऩ༰ΞϓϦͷ࡞੒
    ‣ $SFBUFBOFX9DPEFQSPKFDUͰNBD04"QQΛબ୒͢Δ

    View full-size slide

  18. ػೳ֦ுͷ௥Ճ
    ‣ 'JMF/FX5BSHFU'JOEFS4ZOD&YUFOTJPOΛબ୒ͯ͠௥Ճ͢Δ
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢԼ४උฤʣ

    View full-size slide

  19. ػೳ֦ுͷ௥Ճ
    ‣ 'JMF/FX5BSHFU'JOEFS4ZOD&YUFOTJPOΛબ୒ͯ͠௥Ճ͢Δ
    ‣ Լͷը૾ͷΑ͏ͳΞϥʔτ͕ग़ͨ৔͸"DUJWBUFΛબ୒͢Δ
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢԼ४උฤʣ

    View full-size slide

  20. ऩ༰ΞϓϦͱػೳ֦ுͷόʔδϣϯͱϏϧυόʔδϣϯΛἧ͑Δ
    ऩ༰ΞϓϦ
    ػೳ֦ு
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢԼ४උฤʣ

    View full-size slide

  21. ͜͜·ͰͰɺࠨͷը૾ͷΑ͏ͳιʔεπϦʔʹͳΔ
    'JOEFSػೳ֦ு͸'JOEFS4ZODTXJGUͱ͍͏
    ϑΝΠϧΛத৺ʹهड़͍ͯ͘͠
    4DIFNFΛऩ༰ΞϓϦʹͯ͠Ϗϧυͨ͠৔߹͸ɺ
    'JOEFSػೳ֦ுଆ΋Ϗϧυ͞ΕΔ
    4DIFNFΛ'JOEFSػೳ֦ுʹͨ͠৔߹͸ɺ
    ػೳ֦ுͷΈ͕Ϗϧυ͞ΕΔ
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢԼ४උฤʣ

    View full-size slide

  22. "'JOEFSػೳ֦ு͕༗ޮʹͳ͍ͬͯΔ͔Ͳ͏͔Λ֬ೝ͢Δํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    if FIFinderSyncController.isExtensionEnabled {
    // Finderػೳ֦ு͸ڐՄࡁΈ
    } else {
    // Finderػೳ֦ு͸ڐՄ͞Ε͍ͯͳ͍
    }
    FIFinderSyncController.showExtensionManagementInterface()
    "γεςϜ؀ڥઃఆͷ'JOEFSػೳ֦ுͷϖʔδΛ։͘ํ๏

    View full-size slide

  23. "'JOEFSػೳ֦ு͕؂ࢹ͢ΔϑΥϧμΛࢦఆ͢Δํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    class FinderSync: FIFinderSync {
    override init() {
    super.init()
    let targetPath = "/Users/UserName/Desktop"
    let targetURL = URL(fileURLWithPath: targetPath, isDirectory: true)
    FIFinderSyncController.default().directoryURLs = [targetURL]
    }
    }

    View full-size slide

  24. "؂ࢹԼͷϑΥϧμΛϢʔβ͕ૢ࡞։࢝ʗऴྃͨ͜͠ͱΛݕग़͢Δํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    class FinderSync: FIFinderSync {
    // ؂ࢹԼͷϑΥϧμΛϢʔβ͕ૢ࡞։࢝ͨ͠ࡍʹݺͼग़͞ΕΔ
    override func beginObservingDirectory(at url: URL) {}
    // ؂ࢹԼͷϑΥϧμΛϢʔβ͕ૢ࡞ऴྃͨ͠ࡍʹݺͼग़͞ΕΔ
    override func endObservingDirectory(at url: URL) {}
    }
    url ͸Ϣʔβͷૢ࡞ର৅ͷϑΥϧμύεͰ͋Δ

    View full-size slide

  25. "؂ࢹԼͷΞΠςϜʹόοδΛ͚ͭΔํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    'JOEFSػೳ֦ுͷ5BSHFUʹ"TTFU$BUBMPHΛ௥Ճ͢Δ
    "TTFU$BUBMPHʹόοδͷը૾Λ௥Ճ͢Δ
    *NBHF4J[FºQJYFMT
    %FWJDFT.BD
    4DBMFT4JOHMF4DBMF
    QY
    QY

    View full-size slide

  26. "؂ࢹԼͷΞΠςϜʹόοδΛ͚ͭΔํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    'JOEFS4ZODʹόοδͷొ࿥Λ͢Δ
    class FinderSync: FIFinderSync {
    override init() {
    super.init()
    FFIFinderSyncController.default()
    .setBadgeImage(NSImage, label: String?, forBadgeIdentifier: String)
    }
    }

    View full-size slide

  27. "؂ࢹԼͷΞΠςϜʹόοδΛ͚ͭΔํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    όοδͱΞΠςϜΛඥ͚ͮΔ
    class FinderSync: FIFinderSync {
    override func requestBadgeIdentifier(for url: URL) {
    FIFinderSyncController.default()
    .setBadgeIdentifier(String, for: url)
    }
    }
    ຊདྷ͸ url ͔ΒϑΝΠϧͷ৘ใΛऔಘͯ͠ɺόοδ෇༩ͷ൑அ͢Δ
    ྫ͑͹ɺurl.hasDirectoryPath ͰϑΥϧμ͔Ͳ͏͔ url.pathExtension
    Ͱ֦ுࢠͷ֬ೝ͕Ͱ͖Δ

    View full-size slide

  28. "ΧελϜίϯςΩετϝχϡʔͷ௥Ճํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    class FinderSync: FIFinderSync {
    override func menu(for menu: FIMenuKind) -> NSMenu? {}
    }
    Swift Ͱ͸ύϥϝʔλ໊Λม͑ͯ΋ಉؔ͡਺ͱͯ͠ίϯύΠϧ͞ΕΔͷͰɺ
    menu Λ menuKind ʹมߋ͢Δͱѻ͍΍͍͢
    override func menu(for menuKind: FIMenuKind) -> NSMenu? {}
    ௥Ճ͢ΔͨΊͷ"1*ʹ͍ͭͯ

    View full-size slide

  29. "ΧελϜίϯςΩετϝχϡʔͷ௥Ճํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    override func menu(for menuKind: FIMenuKind) -> NSMenu? {
    let menu = NSMenu(title: "")
    // ϑΥϧμͷഎܠ্ͰӈΫϦοΫΛͨ͠ͱ͖
    if menuKind == .contextualMenuForContainer {
    menu.addItem(withTitle: String, action: Selector?, keyEquivalent: String)
    }
    // ϑΝΠϧΛӈΫϦοΫͨ͠ͱ͖
    if menuKind == .contextualMenuForItems {
    menu.addItem(withTitle: String, action: Selector?, keyEquivalent: String)
    }
    return menu
    }

    View full-size slide

  30. "ΧελϜπʔϧόʔϘλϯͷ௥Ճํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    πʔϧόʔϘλϯͷ໊લɺπʔϧνοϓɺΞΠίϯը૾Λઃఆ͢Δ
    class FinderSync: FIFinderSync {
    // πʔϧόʔϘλϯͷ໊લ
    override var toolbarItemName: String {}
    // πʔϧόʔϘλϯͷπʔϧνοϓ
    override var toolbarItemToolTip: String {}
    // πʔϧόʔϘλϯͷΞΠίϯը૾
    override var toolbarItemImage: NSImage {}
    }

    View full-size slide

  31. "ΧελϜπʔϧόʔϘλϯͷ௥Ճํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    ϘλϯΛԡͨ͠ͱ͖ʹදࣔ͢ΔίϯςΩετϝχϡʔͷࢦఆΛ͢Δ
    override func menu(for menuKind: FIMenuKind) -> NSMenu? {
    let menu = NSMenu(title: "")
    // ΧελϜπʔϧόʔϘλϯΛΫϦοΫͨ͠ͱ͖
    if menuKind == .toolbarItemMenu {
    menu.addItem(withTitle: String, action: Selector?, keyEquivalent: String)
    }
    return menu
    }

    View full-size slide

  32. "ίϯςΩετϝχϡʔʹऩೲΞϓϦͷΞΠίϯΛදࣔͤ͞Δํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    let menuItem = NSMenuItem(title: String,
    action: Selector?,
    keyEquivalent: String)
    let id = "ऩೲΞϓϦͷBundle Identifier"
    if let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: id) {
    menuItem.image = NSWorkspace.shared.icon(forFile: url.path)
    }

    View full-size slide

  33. "Ϣʔβ͕ૢ࡞͍ͯ͠ΔϑΥϧμͷ63-Λऔಘ͢Δํ๏
    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ
    let targetedURL = FIFinderSyncController.default().targetedURL()
    let itemURLs = FIFinderSyncController.default().selectedItemURLs()
    "Ϣʔβ͕બ୒ͨ͠ϑΝΠϧͷ63-Λऔಘ͢Δํ๏
    Ϣʔβ͕ϑΝΠϧΛબ୒ͤͣɺϑΥϧμ্ͰӈΫϦοΫͨ͠৔߹͸ɺ
    .selectedItemURLs() ͸ .targetedURL() ͱಉ͡URLʹͳΔ

    View full-size slide

  34. 'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏

    View full-size slide

  35. 'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏
    ·ͣɺ4XJGUQSJOU
    Λ࢖͏͜ͱ͸Ͱ͖ͳ͍
    ػೳ֦ுͷ1SPDFTTΛ"UUBDIͯ͠΋ඪ४ग़ྗʹදࣔ͞Εͳ͍
    ‣ 9DPEFͰϒϨʔΫϙΠϯτΛ࢖͏ํ๏
    ‣ /4-PHͱίϯιʔϧΛ࢖͏ํ๏
    ‣ (6*Λ࢖͏ํ๏

    View full-size slide

  36. "9DPEFͰϒϨʔΫϙΠϯτΛ࢖͏ํ๏
    'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏
    1.Clean Build Folder Λ࣮ߦ͢Δ
    2.Terminal ͳͲͰ killall Finder Λୟ͘
    3.ऩ༰ΞϓϦΛϏϧυˍ࣮ߦͯ͠ɺγεςϜ؀ڥઃఆͰػೳ֦ுΛ༗ޮʹ͓ͯ͘͠
    4.ίʔυͷ೚ҙͷՕॴʹϒϨʔΫϙΠϯτΛ഑ஔ͢Δ
    5.Finder Sync Extension ͷλʔήοτΛબΜͰϏϧυ͚ͩ͢Δʢ࣮ߦ͸͠ͳ͍ʣ
    6.Finder Ͱ؂ࢹର৅ͷϑΥϧμΛ։͘
    7.Debug -> Attach to process Ͱ࠷΋൪߸ͷେ͖͍ process Λ Attach ͢Δ
    8.௨ৗͷϒϨʔΫϙΠϯτΛ༻͍ͨσόοάΛߦ͏
    9.σόοά͕ࡁΜͩΒ Debug -> Detach from ʓʓ Λͯ͠ Detach ͢Δ

    View full-size slide

  37. "/4-PHͱίϯιʔϧΛ࢖͏ํ๏
    'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏
    ίʔυ಺ͷඪ४ग़ྗ͍ͨ͠ՕॴͰ Swift.print() ͷ୅ΘΓʹ NSLog() Λ࢖͏
    ͜ͷͱ͖ɺग़ྗ͢Δจࣈͷઌ಄ʹֆจࣈΛ͚͓ͭͯ͘
    NSLog("# \(url.path)")
    NSLog("$ check point")

    View full-size slide

  38. "/4-PHͱίϯιʔϧΛ࢖͏ํ๏
    'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏
    Console.app Λ։͖ɺݕࡧόʔʹֆจࣈΛೖྗ͢Δʢෳ਺ಉ࣌ʹೖྗ͕Մೳʣ
    πʔϧόʔͷʮ։࢝ʯΛԡͤ͹ NSLog() ͷग़ྗΛัଊͰ͖ΔΑ͏ʹͳΔ
    $POTPMFBQQ

    View full-size slide

  39. "(6*Λ࢖͏ํ๏
    ‣ /4"MFSUΛ࢖͏
    ‣ ϩʔΧϧ௨஌ʢ6TFS/PUJpDBUJPOTʣΛ࢖͏
    'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏

    View full-size slide

  40. "(6*Λ࢖͏ํ๏/4"MFSU
    'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏
    DispatchQueue.main.async {
    let alert = NSAlert()
    alert.alertStyle = .informational
    alert.messageText = "check point"
    alert.runModal()
    }
    ಈ࡞֬ೝ͍ͨ͠ՕॴʹҎԼͷΑ͏ͳ/4"MFSUදࣔͷίʔυΛهड़͢Δ

    View full-size slide

  41. "(6*Λ࢖͏ํ๏ϩʔΧϧ௨஌ʢ6TFS/PUJpDBUJPOTʣ
    'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏
    ऩ༰ΞϓϦଆͰ௨஌ηϯλʔͷར༻ڐՄͷ֬ೝΛ͢Δ
    UNUserNotificationCenter.current()
    .requestAuthorization(options: [.alert]) { granted, error in
    Swift.print(granted) // true ͳΒڐՄࡁΈ
    }

    View full-size slide

  42. "(6*Λ࢖͏ํ๏ϩʔΧϧ௨஌ʢ6TFS/PUJpDBUJPOTʣ
    'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏
    'JOEFSػೳ֦ுଆͰϩʔΧϧ௨஌Λදࣔͤ͞ΔίʔυΛهड़͢Δ
    private func postUserNotification(subtitle: String, body: String) {
    let content = UNMutableNotificationContent()
    content.title = "SampleExtension"
    content.subtitle = subtitle
    content.body = body
    let request = UNNotificationRequest(identifier: UUID().uuidString,
    content: content,
    trigger: nil)
    UNUserNotificationCenter.current()
    .add(request, withCompletionHandler: nil)
    }
    // ಈ࡞֬ೝ͍ͨ͠Օॴʹهड़
    postUserNotification(subtitle: "check url", body: url.path)

    View full-size slide

  43. ศརπʔϧ࡞੒࣌ͷ5JQT

    View full-size slide

  44. ศརπʔϧ࡞੒࣌ͷ5JQT
    "63-͔ΒϑΝΠϧ໊ʗϑΥϧμ໊Λऔಘ͢Δ
    extension URL {
    var fileName: String {
    return self.deletingPathExtension().lastPathComponent
    }
    }
    URL.deletingPathExtension() Λ࢖͏ͱ֦ுࢠΛআ͍ͨ URL ͕औಘͰ͖Δ

    View full-size slide

  45. ศརπʔϧ࡞੒࣌ͷ5JQT
    "ϑΝΠϧૢ࡞͸'JMF.BOBHFSΛ࢖͍౗͢
    // ϑΥϧμͷ࡞੒
    FileManager.default.createDirectory(at: URL,
    withIntermediateDirectories: Bool)
    // ϑΝΠϧͷҠಈ or ϦωʔϜ
    FileManager.default.moveItem(at: URL, to: URL)
    // ϑΝΠϧͷ࡟আ
    FileManager.default.removeItem(at: URL)

    View full-size slide

  46. ศརπʔϧ࡞੒࣌ͷ5JQT
    "ϑΝΠϧͷ৘ใΛऔಘ͢Δ
    let url = URL(fileURLWithPath: "/Sample.txt")
    let attributes = try? FileManager.default.attributesOfItem(atPath: url.path)
    // ࡞੒೔
    let creationDate = attributes?[FileAttributeKey.creationDate] as? Date
    // มߋ೔
    let modDate = attributes?[FileAttributeKey.modificationDate] as? Data
    // ϑΝΠϧΦʔφʔ໊
    let ownerName = attributes?[FileAttributeKey.ownerAccountName] as? String
    // ϑΝΠϧαΠζʢByteʣ
    let fileSize = attributes?[FileAttributeKey.size] as? NSNumber
    // ࢀর͞Εͨճ਺
    let refCount = attributes?[FileAttributeKey.referenceCount] as? NSNumber

    View full-size slide

  47. ศརπʔϧ࡞੒࣌ͷ5JQT
    "/4"MFSUɾ/40QFO1BOFMɾ/44BWF1BOFMΛ׆༻͢Δ
    ‣ BDDFTTPSZ7JFXΛ࢖ͬͯಠࣗͷΠϯλʔϑΣʔεΛ௥Ճ͢Δ
    let alert = NSAlert()
    alert.messageText = "͜Μʹͪ͸"
    alert.accessoryView = NSView() //%ಠࣗͷView
    alert.runModal()
    "DDFTTPSZ7JFX

    View full-size slide

  48. ศརπʔϧ࡞੒࣌ͷ5JQT
    "/4"MFSUɾ/40QFO1BOFMɾ/44BWF1BOFMΛ׆༻͢Δ
    ‣ BDDFTTPSZ7JFX಺ͷςΩετϑΟʔϧυͰ͸Χοτɾίϐʔɾϖʔετ
    ͳͲͷγϣʔτΧοτ͕ޮ͔ͳ͍ͷͰɺ͓·͡ͳ͍͕ඞཁ
    extension NSTextField {
    open override func performKeyEquivalent(with event: NSEvent) -> Bool {
    let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
    if flags == [.command] {
    let selector: Selector
    switch event.charactersIgnoringModifiers?.lowercased() {
    case "x": selector = #selector(NSText.cut(_:))
    case "c": selector = #selector(NSText.copy(_:))
    case "v": selector = #selector(NSText.paste(_:))
    case "a": selector = #selector(NSText.selectAll(_:))
    case "z": selector = Selector(("undo:"))
    default: return super.performKeyEquivalent(with: event)
    }
    return NSApp.sendAction(selector, to: nil, from: self)
    } else if flags == [.shift, .command] {
    if event.charactersIgnoringModifiers?.lowercased() == "z" {
    return NSApp.sendAction(Selector(("redo:")), to: nil, from: self)
    }
    self.undoManager?.undo()
    }
    return super.performKeyEquivalent(with: event)
    }
    }

    View full-size slide

  49. ศརπʔϧ࡞੒࣌ͷ5JQT
    "/4"MFSUɾ/40QFO1BOFMɾ/44BWF1BOFMΛ׆༻͢Δ
    ‣ BDDFTTPSZ7JFX಺ͷςΩετϑΟʔϧυͰ͸Χοτɾίϐʔɾϖʔετ
    ͳͲͷγϣʔτΧοτ͕ޮ͔ͳ͍ͷͰɺ͓·͡ͳ͍͕ඞཁ
    extension NSTextField {
    open override func performKeyEquivalent(with event: NSEvent) -> Bool {
    let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
    if flags == [.command] {
    let selector: Selector
    switch event.charactersIgnoringModifiers?.lowercased() {
    case "x": selector = #selector(NSText.cut(_:))
    case "c": selector = #selector(NSText.copy(_:))
    case "v": selector = #selector(NSText.paste(_:))
    case "a": selector = #selector(NSText.selectAll(_:))
    case "z": selector = Selector(("undo:"))
    default: return super.performKeyEquivalent(with: event)
    }
    return NSApp.sendAction(selector, to: nil, from: self)
    } else if flags == [.shift, .command] {
    if event.charactersIgnoringModifiers?.lowercased() == "z" {
    return NSApp.sendAction(Selector(("redo:")), to: nil, from: self)
    }
    self.undoManager?.undo()
    }
    return super.performKeyEquivalent(with: event)
    }
    }
    ϝχϡʔόʔ͕ͳ͘ʮฤूʯܥͷ FirstResponder ͱͷܨ͕ΓΛ
    ࣋ͨͳ͍ͨΊɺNSTextField ͷ performKeyEquivalent Ͱ
    γϣʔτΧοτ͝ͱʹॲཧΛׂΓ౰ͯΔඞཁ͕͋Δ

    View full-size slide

  50. 'JOEFS4ZOD&YUFOTJPOͷ஫ҙ఺

    View full-size slide

  51. 'JOEFS4ZOD&YUFOTJPOͷ஫ҙ఺
    'JOEFSػೳ֦ு͸Ϣʔβ͕બ୒ͨ͠ϑΝΠϧͰ͋Δʹ΋͔͔ΘΒͣɺ
    ಡΈॻ͖ݖݶʢ&OUJUMFNFOUTʣ͕௨༻͠ͳ͍৔߹͕͋Δ
    ‣ ҎԼͷ̎ͭ͸Ϣʔβ͕બ୒ͨ͠ΞΠςϜͷ63-Λฦ͕͢ɺॻ͖׵͑ͨΓ
    ࡟আͨ͠Γ͸ͦͷ··Ͱ͸Ͱ͖ͳ͍
    FIFinderSyncController.default().targetedURL()
    FIFinderSyncController.default().selectedItemURLs()()
    ‣ 'JMF"DDFTTͰDPNBQQMFTFDVSJUZpMFTVTFSTFMFDUFESFBEXSJUFΛ
    :&4ʹͯ͠΋ޮ͖໨͕ͳ͍

    View full-size slide

  52. 'JOEFS4ZOD&YUFOTJPOͷ஫ҙ఺
    'JOEFSػೳ֦ு͸Ϣʔβ͕બ୒ͨ͠ϑΝΠϧͰ͋Δʹ΋͔͔ΘΒͣɺ
    ಡΈॻ͖ݖݶʢ&OUJUMFNFOUTʣ͕௨༻͠ͳ͍৔߹͕͋Δ
    㾎 /40QFO1BOFM΍/44BWF1BOFMΛ࢖ͬͯϑΝΠϧύεΛऔಘ͢Ε͹
    ಡΈॻ͖ݖݶ͕௨༻͢Δ
    㾎 "QQ4BOECPY5FNQPSBSZ&YDFQUJPOͰ͋Δ
    DPNBQQMFTFDVSJUZUFNQPSBSZFYDFQUJPOpMFTBCTPMVUFQBUISFBEXSJUF
    ·ͨ͸
    DPNBQQMFTFDVSJUZUFNQPSBSZFYDFQUJPOpMFTIPNFSFMBUJWFQBUISFBEXSJUF
    Λ༻͍ͯ؂ࢹ͢ΔϑΥϧμͷύεΛࢦఆ͢Ε͹ಡΈॻ͖͕ՄೳʹͳΔ

    View full-size slide

  53. 'JOEFS4ZOD&YUFOTJPOͷ஫ҙ఺
    'JOEFSػೳ֦ு͸Ϣʔβ͕બ୒ͨ͠ϑΝΠϧͰ͋Δʹ΋͔͔ΘΒͣɺ
    ಡΈॻ͖ݖݶʢ&OUJUMFNFOUTʣ͕௨༻͠ͳ͍৔߹͕͋Δ
    㾎 /40QFO1BOFM΍/44BWF1BOFMΛ࢖ͬͯϑΝΠϧύεΛऔಘ͢Ε͹
    ಡΈॻ͖ݖݶ͕௨༻͢Δ
    㾎 "QQ4BOECPY5FNQPSBSZ&YDFQUJPOͰ͋Δ
    DPNBQQMFTFDVSJUZUFNQPSBSZFYDFQUJPOpMFTBCTPMVUFQBUISFBEXSJUF
    ·ͨ͸
    DPNBQQMFTFDVSJUZUFNQPSBSZFYDFQUJPOpMFTIPNFSFMBUJWFQBUISFBEXSJUF
    Λ༻͍ͯ؂ࢹ͢ΔϑΥϧμͷύεΛࢦఆ͢Ε͹ಡΈॻ͖͕ՄೳʹͳΔ
    App Store Ͱ഑৴Ͱ͖Δ͔͸৹ࠪһͱͷަব࣍ୈ
    Temporary Exception Λ෇༩͢Δ৔߹͸ɺͳͥͦΕ͕ඞཁͳͷ͔
    ৹ࠪһʹઆ໌͢Δඞཁ͕͋Δ

    View full-size slide

  54. 'JOEFS4ZOD&YUFOTJPOͷ஫ҙ఺
    "QQ4BOECPYԼͰ؂ࢹ͢ΔϑΥϧμΛࢦఆ͢Δͷ͸໽հ
    ‣ "QQ4BOECPY༗ޮ࣌͸ૉ௚ͳύεΛऔಘͰ͖ͳ͍
    let homeDirectoryPath = NSHomeDirectory()
    let desktopPath = FileManager.default
    .urls(for: .desktopDirectory, in: .userDomainMask)
    .first!.path
    // App Sandbox ແޮ࣌
    /Users/UserName
    /Users/UserName/Desktop
    // App Sandbox ༗ޮ࣌
    /Users/UserName/Library/Containers/AppBundleIdentifier/Data
    /Users/UserName/Library/Containers/AppBundleIdentifier/Data/Desktop

    View full-size slide

  55. 'JOEFS4ZOD&YUFOTJPOͷ஫ҙ఺
    "QQ4BOECPYԼͰ؂ࢹ͢ΔϑΥϧμΛࢦఆ͢Δͷ͸໽հ
    ‎ %BSXJOͷ"1*Ͱڧ੍తʹϢʔβͷϗʔϜϑΥϧμύεΛऔಘՄೳ
    if let pw = getpwuid(getuid()), let home = pw.pointee.pw_dir {
    let homePath = FileManager.default
    .string(withFileSystemRepresentation: home, length: strlen(home))
    // homePath => /Users/UserName
    let targetURL = URL(fileURLWithPath: homePath)
    .appendingPathComponent("Desktop")
    FIFinderSyncController.default().directoryURLs = [targetURL]
    }

    View full-size slide

  56. 'JOEFS4ZOD&YUFOTJPOͷ஫ҙ఺
    ΧελϜπʔϧόʔϘλϯ͸Ϣʔβ͕ҙਤతʹઃఆ͠ͳ͍ͱදࣔ͞Εͳ͍

    View full-size slide

  57. 'JOEFS4ZOD&YUFOTJPOͷ஫ҙ఺
    'JOEFSػೳ֦ுͲ͏͕͠ڝ߹͢Δ͜ͱ͕͋Δ
    ‣ ؂ࢹର৅ͱ͍ͯ͠ΔϑΥϧμʢ͓Αͼͦͷ֊૚Լʣ͕ॏͳ͍ͬͯΔ৔߹ɺ
    ઌʹ༗ޮʹ͞Εͨ'JOEFSػೳ֦ு͕༏ઌ͞ΕΔ
    ‎ ڝ߹ͨ͠৔߹ɺόοδͷදࣔػೳ͕ແޮʹͳΔ
    γεςϜ؀ڥઃఆͰ Finder ػೳ֦ுͷ༗ޮແޮΛखಈ੾Γସ͑
    ͯ͠ɺ༏ઌॱҐΛௐ੔͢Δඞཁ͕͋Δ

    View full-size slide

  58. ศརπʔϧͷ࣮૷ྫ

    View full-size slide

  59. ศརπʔϧͷ࣮૷ྫ̍
    4DBMF)FMQFSʙ։ൃͰͷը૾ϦαΠζΛख఻͏πʔϧʙ
    ιʔεˍμ΢ϯϩʔυɿIUUQTHJUIVCDPN,ZPNF4DBMF)FMQFS
    ը૾ϑΝΠϧͷ໊લͷ຤ඌʹʮ!Yʯ΍ʮ!Yʯ͕͍͍ͭͯΔ
    ৔߹ɺͦΕΛ໌ࣔ͢Δόοδ͕දࣔ͞ΕɺίϯςΩετϝχϡʔ
    ͔Β̍ഒ΍̎ഒαΠζͷը૾Λੜ੒Ͱ͖Δ
    όοδදࣔ
    ίϯςΩετϝχϡʔʹΑΔλεΫ࣮ߦ

    View full-size slide

  60. ศརπʔϧͷ࣮૷ྫ̍4DBMF)FMQFSσϞ
    ಈը

    View full-size slide

  61. ศརπʔϧͷ࣮૷ྫ̎
    3FOBNF)FMQFSʙ04ඪ४ͷ໊শมߋͷ଍Γͳ͍ॴΛิ͏πʔϧʙ
    ιʔεˍμ΢ϯϩʔυɿIUUQTHJUIVCDPN,ZPNF3FOBNF)FMQFS
    ෳ਺ϑΝΠϧબ୒࣌ɺίϯςΩετϝχϡʔ͔Βಛఆͷϧʔϧʹ
    ैͬͯ࿈൪ͰϦωʔϜΛߦ͑Δ
    ίϯςΩετϝχϡʔʹΑΔλεΫ࣮ߦ
    /4"MFSU͓Αͼ"DDFTTPSZ7JFXΛ׆༻

    View full-size slide

  62. ศརπʔϧͷ࣮૷ྫ̍3FOBNF)FMQFSσϞ
    ಈը

    View full-size slide

  63. ศརπʔϧͷ࣮૷ྫ̏
    /FX$BOWBTʙ1SFWJFXBQQͷᙱ͍ॴʹखΛಧ͔ͤΔπʔϧʙ
    ιʔεˍμ΢ϯϩʔυɿIUUQTHJUIVCDPN,ZPNF/FX$BOWBT
    σεΫτοϓ΍'JOEFS্ʹ͓͍ͯɺίϯςΩετϝχϡʔ΍
    πʔϧόʔϘλϯ͔Βແ஍ͷը૾ϑΝΠϧΛੜ੒ͯ͠
    1SFWJFXBQQͰ։͚Δ
    ίϯςΩετϝχϡʔʹΑΔλεΫ࣮ߦ
    ΧελϜπʔϧόʔͷ௥Ճ
    /44BWF1BOFM͓Αͼ"DDFTTPSZ7JFXΛ׆༻

    View full-size slide

  64. ศརπʔϧͷ࣮૷ྫ̍/FX$BOWBTσϞ
    ಈը

    View full-size slide

  65. ·ͱΊ
    'JOEFS4ZOD&YUFOTJPO͸ৗறܕϢʔςΟϦςΟπʔϧͷ࡞੒ʹͽͬͨΓʂ
    ‣ ࢦఆͨ͠ϑΥϧμ಺ͷঢ়ଶ؂ࢹ
    ‣ ϑΝΠϧ΍ϑΥϧμ΁ͷόοδͷ෇༩
    ‣ ΧελϜίϯςΩετϝχϡʔʹΑΔλεΫ࣮ߦ
    ‣ ΧελϜπʔϧόʔϘλϯʹΑΔλεΫ࣮ߦ
    σόοάํ๏͕ಛघ
    "QQ,JUͷμΠΞϩάܥ6*Λ࢖ָ࣮ͬͯͯ͠૷
    ϑΝΠϧͷಡΈॻ͖ݖݶʹ͸஫ҙʂ

    View full-size slide

  66. 'JOEFS4ZOD&YUFOTJPOͰ
    ศརπʔϧΛ࡞Δͱ͜Ζ͔Βɺ
    NBD04ΞϓϦ։ൃ࢝ΊͯΈ·ͤΜ͔ʁ
    5IBOL:PV

    View full-size slide