Open22

macOSの豆知識

IKEHIKEH

ビデオ再生するだけのビュー

  • 特筆すべきは下記の設定くらい

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.
        }
    }
    
    
}
IKEHIKEH

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 % 
IKEHIKEH

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 % 
IKEHIKEH

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}")
    }
}
IKEHIKEH

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()
}
IKEHIKEH

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()
IKEHIKEH

NSViewControllerをnibから読み込む

let myViewController = MyViewController(nibName: "MyViewController", bundle: nil)
self.present(myViewController, animated: true, completion: nil)
IKEHIKEH

アクセス権を変更

let attributes: [FileAttributeKey: AnyObject] = [FileAttributeKey.posixPermissions: NSNumber(value: 0o775)]
do {
    try fileManager.setAttributes(attributes, ofItemAtPath: exportUrl.path)
} catch {
    NSAlert.showMessage(messageText: error.localizedDescription)
    return
}
IKEHIKEH

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

IKEHIKEH

.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
    }
    
}
IKEHIKEH

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()
    }
}
IKEHIKEH

メール作成

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)"
    }
}
KyomeKyome

開発者への連絡フォームですよね。私も似たようなことやってますね。

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
    }
}
IKEHIKEH

ですです!
先日きょめさんに連絡フォームの実装を勧めてもらったので実装を考えてみました。
(文言に関してはRunCatとCotEditorのものを参考にしました!)

infoStringのextensionは便利そうでいいですね、参考にさせてもらいます〜。