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

Dc8cb7f6b08b47c50576d7e73ea136ba?s=128

Kyome (Takuto Nakamura)

September 18, 2021
Tweet

Transcript

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

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

  3. 'JOEFS4ZOD&YUFOTJPOͱ͸

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

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

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

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

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

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

  10. Ͱ΋͜ͷࢠɺ

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

  12. ࢲͷղऍ

  13. ࢲͷղऍ  Ϣʔβ͕ಛఆͷϑΥϧμΛૢ࡞࢝͠Ίͨ͜ͱΛݕ஌Ͱ͖Δ  ಉظͱؔ܎ͳ͘ɺϑΝΠϧ΍ϑΥϧμͷঢ়ଶʹैͬͯόοδΛ෇༩Ͱ͖Δ  'JOEFS্Ͱબ୒தͷϑΝΠϧ΍ϑΥϧμʹରͯ͠೚ҙͷλεΫΛ࣮ߦͰ͖Δ  ΋͸΍ɺબ୒தͷϑΝΠϧ΍ϑΥϧμͱؔ܎ͳ͘ɺ೚ҙͷϧʔνϯλεΫΛ ࣗಈԽͨ͠ίϚϯυΛୟ͚Δ

    ‎ ϝχϡʔόʔৗறܕͱ͸ҟͳΔܗͷ ɹɹɹɹɹɹৗறܕϢʔςΟϦςΟπʔϧͷ࡞੒ʹ࢖͑Δ!
  14. 'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊ

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

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

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

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

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

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

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

  22. ͜͜·ͰͰɺࠨͷը૾ͷΑ͏ͳιʔεπϦʔʹͳΔ  'JOEFSػೳ֦ு͸'JOEFS4ZODTXJGUͱ͍͏ ϑΝΠϧΛத৺ʹهड़͍ͯ͘͠  4DIFNFΛऩ༰ΞϓϦʹͯ͠Ϗϧυͨ͠৔߹͸ɺ 'JOEFSػೳ֦ுଆ΋Ϗϧυ͞ΕΔ  4DIFNFΛ'JOEFSػೳ֦ுʹͨ͠৔߹͸ɺ ػೳ֦ுͷΈ͕Ϗϧυ͞ΕΔ

    'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢԼ४උฤʣ
  23. "'JOEFSػೳ֦ு͕༗ޮʹͳ͍ͬͯΔ͔Ͳ͏͔Λ֬ೝ͢Δํ๏ 'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ if FIFinderSyncController.isExtensionEnabled { // Finderػೳ֦ு͸ڐՄࡁΈ } else {

    // Finderػೳ֦ு͸ڐՄ͞Ε͍ͯͳ͍ } FIFinderSyncController.showExtensionManagementInterface() "γεςϜ؀ڥઃఆͷ'JOEFSػೳ֦ுͷϖʔδΛ։͘ํ๏
  24. "'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] } }
  25. "؂ࢹԼͷϑΥϧμΛϢʔβ͕ૢ࡞։࢝ʗऴྃͨ͜͠ͱΛݕग़͢Δํ๏ 'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ class FinderSync: FIFinderSync { // ؂ࢹԼͷϑΥϧμΛϢʔβ͕ૢ࡞։࢝ͨ͠ࡍʹݺͼग़͞ΕΔ override func

    beginObservingDirectory(at url: URL) {} // ؂ࢹԼͷϑΥϧμΛϢʔβ͕ૢ࡞ऴྃͨ͠ࡍʹݺͼग़͞ΕΔ override func endObservingDirectory(at url: URL) {} } url ͸Ϣʔβͷૢ࡞ର৅ͷϑΥϧμύεͰ͋Δ
  26. "؂ࢹԼͷΞΠςϜʹόοδΛ͚ͭΔํ๏ 'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ  'JOEFSػೳ֦ுͷ5BSHFUʹ"TTFU$BUBMPHΛ௥Ճ͢Δ  "TTFU$BUBMPHʹόοδͷը૾Λ௥Ճ͢Δ  *NBHF4J[FºQJYFMT  %FWJDFT.BD

     4DBMFT4JOHMF4DBMF QY QY
  27. "؂ࢹԼͷΞΠςϜʹόοδΛ͚ͭΔํ๏ 'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ  'JOEFS4ZODʹόοδͷొ࿥Λ͢Δ class FinderSync: FIFinderSync { override init()

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

    requestBadgeIdentifier(for url: URL) { FIFinderSyncController.default() .setBadgeIdentifier(String, for: url) } } ຊདྷ͸ url ͔ΒϑΝΠϧͷ৘ใΛऔಘͯ͠ɺόοδ෇༩ͷ൑அ͢Δ ྫ͑͹ɺurl.hasDirectoryPath ͰϑΥϧμ͔Ͳ͏͔ url.pathExtension Ͱ֦ுࢠͷ֬ೝ͕Ͱ͖Δ
  29. "ΧελϜίϯςΩετϝχϡʔͷ௥Ճํ๏ 'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ class FinderSync: FIFinderSync { override func menu(for menu:

    FIMenuKind) -> NSMenu? {} } Swift Ͱ͸ύϥϝʔλ໊Λม͑ͯ΋ಉؔ͡਺ͱͯ͠ίϯύΠϧ͞ΕΔͷͰɺ menu Λ menuKind ʹมߋ͢Δͱѻ͍΍͍͢ override func menu(for menuKind: FIMenuKind) -> NSMenu? {}  ௥Ճ͢ΔͨΊͷ"1*ʹ͍ͭͯ
  30. "ΧελϜίϯςΩετϝχϡʔͷ௥Ճํ๏ '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 }
  31. "ΧελϜπʔϧόʔϘλϯͷ௥Ճํ๏ 'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ  πʔϧόʔϘλϯͷ໊લɺπʔϧνοϓɺΞΠίϯը૾Λઃఆ͢Δ class FinderSync: FIFinderSync { // πʔϧόʔϘλϯͷ໊લ

    override var toolbarItemName: String {} // πʔϧόʔϘλϯͷπʔϧνοϓ override var toolbarItemToolTip: String {} // πʔϧόʔϘλϯͷΞΠίϯը૾ override var toolbarItemImage: NSImage {} }
  32. "ΧελϜπʔϧόʔϘλϯͷ௥Ճํ๏ '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 }
  33. "ίϯςΩετϝχϡʔʹऩೲΞϓϦͷΞΠίϯΛදࣔͤ͞Δํ๏ '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) }
  34. "Ϣʔβ͕ૢ࡞͍ͯ͠ΔϑΥϧμͷ63-Λऔಘ͢Δํ๏ 'JOEFS4ZOD&YUFOTJPO࣮૷ͷجຊʢػೳผฤʣ let targetedURL = FIFinderSyncController.default().targetedURL() let itemURLs = FIFinderSyncController.default().selectedItemURLs()

    "Ϣʔβ͕બ୒ͨ͠ϑΝΠϧͷ63-Λऔಘ͢Δํ๏ Ϣʔβ͕ϑΝΠϧΛબ୒ͤͣɺϑΥϧμ্ͰӈΫϦοΫͨ͠৔߹͸ɺ .selectedItemURLs() ͸ .targetedURL() ͱಉ͡URLʹͳΔ
  35. 'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏

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

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

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

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

  41. "(6*Λ࢖͏ํ๏/4"MFSU 'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏ DispatchQueue.main.async { let alert = NSAlert() alert.alertStyle =

    .informational alert.messageText = "check point" alert.runModal() } ಈ࡞֬ೝ͍ͨ͠ՕॴʹҎԼͷΑ͏ͳ/4"MFSUදࣔͷίʔυΛهड़͢Δ
  42. "(6*Λ࢖͏ํ๏ϩʔΧϧ௨஌ʢ6TFS/PUJpDBUJPOTʣ 'JOEFS4ZOD&YUFOTJPOͷσόοάํ๏  ऩ༰ΞϓϦଆͰ௨஌ηϯλʔͷར༻ڐՄͷ֬ೝΛ͢Δ UNUserNotificationCenter.current() .requestAuthorization(options: [.alert]) { granted, error

    in Swift.print(granted) // true ͳΒڐՄࡁΈ }
  43. "(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)
  44. ศརπʔϧ࡞੒࣌ͷ5JQT

  45. ศརπʔϧ࡞੒࣌ͷ5JQT "63-͔ΒϑΝΠϧ໊ʗϑΥϧμ໊Λऔಘ͢Δ extension URL { var fileName: String { return

    self.deletingPathExtension().lastPathComponent } } URL.deletingPathExtension() Λ࢖͏ͱ֦ுࢠΛআ͍ͨ URL ͕औಘͰ͖Δ
  46. ศརπʔϧ࡞੒࣌ͷ5JQT "ϑΝΠϧૢ࡞͸'JMF.BOBHFSΛ࢖͍౗͢ // ϑΥϧμͷ࡞੒ FileManager.default.createDirectory(at: URL, withIntermediateDirectories: Bool) // ϑΝΠϧͷҠಈ

    or ϦωʔϜ FileManager.default.moveItem(at: URL, to: URL) // ϑΝΠϧͷ࡟আ FileManager.default.removeItem(at: URL)
  47. ศརπʔϧ࡞੒࣌ͷ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
  48. ศརπʔϧ࡞੒࣌ͷ5JQT "/4"MFSUɾ/40QFO1BOFMɾ/44BWF1BOFMΛ׆༻͢Δ ‣ BDDFTTPSZ7JFXΛ࢖ͬͯಠࣗͷΠϯλʔϑΣʔεΛ௥Ճ͢Δ let alert = NSAlert() alert.messageText =

    "͜Μʹͪ͸" alert.accessoryView = NSView() //%ಠࣗͷView alert.runModal() "DDFTTPSZ7JFX
  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) } }
  50. ศརπʔϧ࡞੒࣌ͷ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 Ͱ γϣʔτΧοτ͝ͱʹॲཧΛׂΓ౰ͯΔඞཁ͕͋Δ
  51. 'JOEFS4ZOD&YUFOTJPOͷ஫ҙ఺

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

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

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

    ·ͨ͸ DPNBQQMFTFDVSJUZUFNQPSBSZFYDFQUJPOpMFTIPNFSFMBUJWFQBUISFBEXSJUF Λ༻͍ͯ؂ࢹ͢ΔϑΥϧμͷύεΛࢦఆ͢Ε͹ಡΈॻ͖͕ՄೳʹͳΔ App Store Ͱ഑৴Ͱ͖Δ͔͸৹ࠪһͱͷަব࣍ୈ Temporary Exception Λ෇༩͢Δ৔߹͸ɺͳͥͦΕ͕ඞཁͳͷ͔ ৹ࠪһʹઆ໌͢Δඞཁ͕͋Δ
  55. '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
  56. '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] }
  57. 'JOEFS4ZOD&YUFOTJPOͷ஫ҙ఺  ΧελϜπʔϧόʔϘλϯ͸Ϣʔβ͕ҙਤతʹઃఆ͠ͳ͍ͱදࣔ͞Εͳ͍

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

    ػೳ֦ுͷ༗ޮແޮΛखಈ੾Γସ͑ ͯ͠ɺ༏ઌॱҐΛௐ੔͢Δඞཁ͕͋Δ
  59. ศརπʔϧͷ࣮૷ྫ

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

    ίϯςΩετϝχϡʔʹΑΔλεΫ࣮ߦ
  61. ศརπʔϧͷ࣮૷ྫ̍4DBMF)FMQFSσϞ ಈը

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

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

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

    ΧελϜπʔϧόʔͷ௥Ճ  /44BWF1BOFM͓Αͼ"DDFTTPSZ7JFXΛ׆༻
  65. ศརπʔϧͷ࣮૷ྫ̍/FX$BOWBTσϞ ಈը

  66. ·ͱΊ

  67. ·ͱΊ  'JOEFS4ZOD&YUFOTJPO͸ৗறܕϢʔςΟϦςΟπʔϧͷ࡞੒ʹͽͬͨΓʂ ‣ ࢦఆͨ͠ϑΥϧμ಺ͷঢ়ଶ؂ࢹ ‣ ϑΝΠϧ΍ϑΥϧμ΁ͷόοδͷ෇༩ ‣ ΧελϜίϯςΩετϝχϡʔʹΑΔλεΫ࣮ߦ ‣

    ΧελϜπʔϧόʔϘλϯʹΑΔλεΫ࣮ߦ  σόοάํ๏͕ಛघ  "QQ,JUͷμΠΞϩάܥ6*Λ࢖ָ࣮ͬͯͯ͠૷  ϑΝΠϧͷಡΈॻ͖ݖݶʹ͸஫ҙʂ
  68. 'JOEFS4ZOD&YUFOTJPOͰ ศརπʔϧΛ࡞Δͱ͜Ζ͔Βɺ NBD04ΞϓϦ։ൃ࢝ΊͯΈ·ͤΜ͔ʁ 5IBOL:PV