Open22
macOSの豆知識

- Macでウィンドウをいい感じに配置するツール再現してみた。
- AXUIElement Set Attribute Value(_:_:_ :)
-
Miscellaneous Defines
- kAX-の定義一覧
- 最前面にするアイディア(フォーカスを与えるのはいけるけど最前面固定はこれでは無理そう)

ビデオ再生するだけのビュー
- Appleの公式チュートリアルがあったので触ってみるだけ
- 特筆すべきは下記の設定くらい
import Cocoa
import AVKit
import AVFoundation
class ViewController: NSViewController {
@IBOutlet weak var playerView: AVPlayerView!
override func viewDidLoad() {
super.viewDidLoad()
guard let url = Bundle.main.url(forResource: "名称未設定", withExtension: "mov") else {
return
}
// Create a new AVPlayer and associate it with the player view.
let player = AVPlayer(url: url)
playerView.player = player
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}

pod initでエラー
参考
現象
XXX % pod init
――― MARKDOWN TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
### Command
/usr/local/bin/pod init
### Error
RuntimeError - [Xcodeproj] Unknown object version.
/Library/Ruby/Gems/2.6.0/gems/xcodeproj-1.19.0/lib/xcodeproj/project.rb:227:in `initialize_from_file'
/Library/Ruby/Gems/2.6.0/gems/xcodeproj-1.19.0/lib/xcodeproj/project.rb:112:in `open'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.0/lib/cocoapods/command/init.rb:41:in `validate!'
/Library/Ruby/Gems/2.6.0/gems/claide-1.0.3/lib/claide/command.rb:333:in `run'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.0/lib/cocoapods/command.rb:52:in `run'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.0/bin/pod:55:in `<top (required)>'
/usr/local/bin/pod:23:in `load'
/usr/local/bin/pod:23:in `<main>'
――― TEMPLATE END ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
[!] Oh no, an error occurred.
Search for existing GitHub issues similar to yours:
(略)
and 70 more at:
https://github.com/cocoapods/cocoapods/search?q=%5BXcodeproj%5D%20Unknown%20object%20version.&type=Issues&utf8=✓
XXX %
XXX % pod setup
Setup completed
XXX % gem install cocoapods
Fetching cocoapods-1.11.2.gem
Fetching addressable-2.8.0.gem
Fetching xcodeproj-1.21.0.gem
Fetching cocoapods-core-1.11.2.gem
Fetching molinillo-0.8.0.gem
Fetching rexml-3.2.5.gem
ERROR: While executing gem ... (Gem::FilePermissionError)
You don't have write permissions for the /Library/Ruby/Gems/2.6.0 directory.
XXX % sudo gem install cocoapods
Password:
Fetching addressable-2.8.0.gem
Fetching cocoapods-core-1.11.2.gem
Fetching xcodeproj-1.21.0.gem
Fetching molinillo-0.8.0.gem
Fetching cocoapods-1.11.2.gem
Fetching rexml-3.2.5.gem
Successfully installed addressable-2.8.0
Successfully installed cocoapods-core-1.11.2
Successfully installed molinillo-0.8.0
Successfully installed rexml-3.2.5
ERROR: While executing gem ... (Gem::FilePermissionError)
You don't have write permissions for the /usr/bin directory.
対応
- cocoapodsの再インストールで解消
XXX % sudo gem install -n /usr/local/bin cocoapods
Successfully installed xcodeproj-1.21.0
Successfully installed cocoapods-1.11.2
Parsing documentation for xcodeproj-1.21.0
Installing ri documentation for xcodeproj-1.21.0
Parsing documentation for cocoapods-1.11.2
Installing ri documentation for cocoapods-1.11.2
Done installing documentation for xcodeproj, cocoapods after 4 seconds
2 gems installed
XXX % pod setup
Setup completed
XXX % pod init
XXX %

pod initでエラー
参考
現象
XXX % pod init
――― MARKDOWN TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
### Command
(略)
### Error
RuntimeError - [Xcodeproj] Unknown object version.
/Library/Ruby/Gems/2.6.0/gems/xcodeproj-1.19.0/lib/xcodeproj/project.rb:227:in `initialize_from_file'
/Library/Ruby/Gems/2.6.0/gems/xcodeproj-1.19.0/lib/xcodeproj/project.rb:112:in `open'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.0/lib/cocoapods/command/init.rb:41:in `validate!'
/Library/Ruby/Gems/2.6.0/gems/claide-1.0.3/lib/claide/command.rb:333:in `run'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.0/lib/cocoapods/command.rb:52:in `run'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.0/bin/pod:55:in `<top (required)>'
/usr/local/bin/pod:23:in `load'
/usr/local/bin/pod:23:in `<main>'
――― TEMPLATE END ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
[!] Oh no, an error occurred.
対応
- cocoapodsの再インストールで解消
XXX % sudo gem install -n /usr/local/bin cocoapods
Successfully installed xcodeproj-1.21.0
Successfully installed cocoapods-1.11.2
Parsing documentation for xcodeproj-1.21.0
Installing ri documentation for xcodeproj-1.21.0
Parsing documentation for cocoapods-1.11.2
Installing ri documentation for cocoapods-1.11.2
Done installing documentation for xcodeproj, cocoapods after 4 seconds
2 gems installed
XXX % pod setup
Setup completed
XXX % pod init
XXX %

過去のmacOSのスクリーンショット

Formatterを触る
参考
コード例
- IBで
NSFormatter
しかない?のかプログラム上で設定した。
@IBOutlet weak var customURLFormatTextField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
let formatter = CustomURLFormatFormatter()
customURLFormatTextField.formatter = formatter
}
- NSTextField上でEnterをすると空になったり挙動がおかしくなったので一旦採用保留。
import AppKit
class CustomURLFormatFormatter: Formatter {
// MARK: - Override
override func string(for obj: Any?) -> String? {
guard let customFormat = obj as? String else {
return nil
}
return customFormat
}
override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?,
for string: String,
errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
return string.contains("{url}")
}
}

NSTableViewの選択時の背景色を変更する
class MyNSTableRowView: NSTableRowView {
override func drawSelection(in dirtyRect: NSRect) {
if self.selectionHighlightStyle != .none {
let selectionRect = NSInsetRect(self.bounds, 2.5, 2.5)
NSColor(calibratedWhite: 0.65, alpha: 1).setStroke()
NSColor(calibratedWhite: 0.82, alpha: 1).setFill()
let selectionPath = NSBezierPath.init(roundedRect: selectionRect, xRadius: 6, yRadius: 6)
selectionPath.fill()
selectionPath.stroke()
}
}
}
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
return MyNSTableRowView()
}

NSViewをnibから読み込む
protocol NibLoadable {
// Name of the nib file
static var nibName: String { get }
static func createFromNib(in bundle: Bundle) -> Self
}
extension NibLoadable where Self: NSView {
// Default nib name must be same as class name
static var nibName: String {
return String(describing: Self.self)
}
static func createFromNib(in bundle: Bundle = Bundle.main) -> Self {
var topLevelArray: NSArray? = nil
bundle.loadNibNamed(NSNib.Name(nibName), owner: self, topLevelObjects: &topLevelArray)
let views = Array<Any>(topLevelArray!).filter { $0 is Self }
return views.last as! Self
}
}
- e.g.
let detailImageView = DetailImageView.createFromNib()

NSViewControllerをnibから読み込む
let myViewController = MyViewController(nibName: "MyViewController", bundle: nil)
self.present(myViewController, animated: true, completion: nil)

アクセス権を変更
let attributes: [FileAttributeKey: AnyObject] = [FileAttributeKey.posixPermissions: NSNumber(value: 0o775)]
do {
try fileManager.setAttributes(attributes, ofItemAtPath: exportUrl.path)
} catch {
NSAlert.showMessage(messageText: error.localizedDescription)
return
}

証明書のエクスポート?
Xcode → Preferences → Accounts → <企業名> → Manage Certificates
のDeveloper ID Application Certificatesをexport

- いずれにせよ、選択範囲に対する処理くらいしかできなさそう?
- Xcode Pluginは廃止?
- アクセス修飾子のつけ忘れを防ぐXcode Source Editor Extensionを作ってみた
- iOSDC Japan 2021: Source Editor ExtensionとSwiftSyntaxでコード自動生成ツー… / 林和弘

Show in Finder...の実装
- Show folder's contents in finder using Swift
- activateFileViewerSelecting
- 複数選択できる

.plistの読み込み
struct Plist {
let url: URL
var contents: String? {
do {
if !FileManager.default.fileExists(atPath: url.path) {
return "(not found)"
}
guard let plistContent = try NSDictionary(contentsOf: url, error: ()) as? Dictionary<String, Any> else {
return nil
}
return (plistContent as NSDictionary).description
} catch {
print(error.localizedDescription)
return nil
}
}
var exists: Bool {
return FileManager.default.fileExists(atPath: url.path)
}
}
or
extension String {
init?(contentsOfPlist url: URL) {
guard let contents = try? NSDictionary(contentsOf: url, error: ()) as? Dictionary<String, Any> else {
return nil
}
self = (contents as NSDictionary).description
}
}

- menubarの並び順の記憶場所
defaults find "NSStatusItem Preferred Position"

NSOpenPanel -> SwiftUI
import SwiftUI
struct FileImporterView: View {
@State private var importerPresented = false
var body: some View {
VStack {
Button("Open") {
importerPresented = true
}
.padding()
}
.fileImporter(isPresented: $importerPresented,
allowedContentTypes: [.directory, .text],
allowsMultipleSelection: true, onCompletion: { result in
switch result {
case .success(let url):
print(url)
case .failure:
print("failure")
}
})
}
}
struct FileImporterView_Previews: PreviewProvider {
static var previews: some View {
FileImporterView()
}
}

メール作成
import AppKit
struct ReportIssueManager {
// MARK: - Private
private static let emailAddress = "<your-email-address>"
private static var systemVersion: String {
let versions = ProcessInfo.processInfo.operatingSystemVersion
return "\(versions.majorVersion).\(versions.minorVersion).\(versions.patchVersion)"
}
private static var currentAppVersion: String {
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "(App Version)"
}
private static var issueContents: String {
createIssueContents(
appName: Bundle.main.appDisplayName,
appVersion: currentAppVersion,
systemVersion: systemVersion)
}
private static func createIssueContents(appName: String, appVersion: String, systemVersion: String) -> String {
return
"""
# Environment
- \(appName): ver \(appVersion)
- macOS: \(systemVersion)
# What You Tried
- Relaunched the app: Yes/No
- Restored at Runners Store: Yes/No
# Short Description
# Steps to Reproduce the Issue
# Expected Result
"""
}
// MARK: - Public
public static func openMail() {
guard let service = NSSharingService(named: NSSharingService.Name.composeEmail) else {
return
}
service.recipients = [emailAddress] // 宛先
service.subject = "\(Bundle.main.appDisplayName) Issue Report" // 件名
service.perform(withItems: [issueContents]) // 本文
}
}
private extension Bundle {
var appDisplayName: String {
if let displayName = object(forInfoDictionaryKey: "CFBundleDisplayName") as? String {
return displayName
} else if let bundleName = object(forInfoDictionaryKey: "CFBundleName") as? String {
return bundleName
}
return "(App Name)"
}
}

開発者への連絡フォームですよね。私も似たようなことやってますね。
protocol IssueReportModel {
static func send()
}
struct IssueReportModelImpl: IssueReportModel {
static func send() {
let appName = "CFBundleName".infoString
let appVersion = "CFBundleShortVersionString".infoString
let os = ProcessInfo.processInfo.operatingSystemVersion
let systemVersion = "\(os.majorVersion).\(os.minorVersion).\(os.patchVersion)"
let service = NSSharingService(named: NSSharingService.Name.composeEmail)!
service.recipients = ["メールアドレス"]
service.subject = "\(appName) \("issueReport".localized)"
service.perform(withItems: [
String(format: "environment".localized, appName, appVersion, systemVersion),
"whatYouTried".localized,
"shortDescription".localized,
"reproduceIssue".localized,
"expectedResult".localized
])
}
}
extension String {
var localized: String {
return NSLocalizedString(self, comment: self)
}
var infoString: String {
guard let str = Bundle.main.object(forInfoDictionaryKey: self) as? String else {
fatalError("infoString key is not found.")
}
return str
}
}

ですです!
先日きょめさんに連絡フォームの実装を勧めてもらったので実装を考えてみました。
(文言に関してはRunCatとCotEditorのものを参考にしました!)
infoStringのextensionは便利そうでいいですね、参考にさせてもらいます〜。