- 投稿日:2020-08-23T19:04:28+09:00
CIImageをCVPixelBufferに
CIImageをCVPixelBufferにします。
1、CVPixelBufferをつくります。
var pixelBuffer: CVPixelBuffer? let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary let width:Int = 256 let height:Int = 256 // 欲しいサイズ CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, attrs, &pixelBuffer)2,CVPixelBufferにCIImageをレンダリングします。
let context = CIContext() context.render(ciImage, to: pixelBuffer!)extensionにしておくと、CIImage.pixelBuffer( )でかんたんにPixelBufferを取得できます。
extension CIImage { func pixelBuffer(cgSize size:CGSize) -> CVPixelBuffer? { var pixelBuffer: CVPixelBuffer? let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary let width:Int = Int(size.width) let height:Int = Int(size.height) CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, attrs, &pixelBuffer) // put bytes into pixelBuffer let context = CIContext() context.render(self, to: pixelBuffer!) return pixelBuffer } }
Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-08-23T18:15:37+09:00
最小限のSwiftプログラムを作ってみる
通常、macOSのプログラムを開発するには、Xcodeを使う。プロジェクトを新規作成すると、テンプレートによってアプリケーションの雛型が作成される。
が、このような雛型は、その中で何をしているのかよくわからないという欠点がある。そこで、最小限のSwiftプログラムを書いてみる。以下を参考にした。
https://gist.github.com/habemus-papadum/11f069b6d074f4bca794bf93994325331. Swiftインタープリタ
まず、以下のファイルを作成する。
take1.swiftprint("Hello, world!")これも有効なSwiftプログラムである。コマンドラインから実行してみる。
% swift take1.swift Hello, world!次に、ウィンドウを表示してみる。
take2.swiftimport Cocoa let window = NSWindow(contentRect: NSMakeRect(0, 0, 320, 200), styleMask: .titled, backing: .buffered, defer: true) window.orderFrontRegardless() NSApp.run()左下に黒いウィンドウが表示されたはずである。これが最小限のウィンドウを表示するプログラムではないか。
次に、もう少しCocoaアプリっぽいプログラムにしてみる。
take3.swiftimport Cocoa let _ = NSApplication.shared class AppDel: NSObject, NSApplicationDelegate { var mainWindow: NSWindow? func applicationDidFinishLaunching(_ aNotification: Notification) { let window = NSWindow(contentRect: NSMakeRect(800, 600, 320, 200), styleMask: [.titled, .closable], backing: .buffered, defer: true) window.orderFrontRegardless() self.mainWindow = window NSApp.activate(ignoringOtherApps: true) } func applicationShouldTerminateAfterLastWindowClosed(_ app: NSApplication) -> Bool { return true } } NSApp.setActivationPolicy(.regular) let del = AppDel() NSApp.delegate = del NSApp.run()実行してみる。
% swift take3.swift take3.swift:2:15: warning: expression of type 'NSApplication' is unused NSApplication.shared ~~~~~~~~~~~~~~^~~~~~さきほどと同様に、黒いウィンドウが表示される。
ただ、warningが出る。どうすればいいだろう。コメントでご指摘をいただき、warningは消すことができました。ありがとうございます。2. Swiftコンパイラ
これを、コンパイラで実行ファイルを作り、実行してみよう。
% swiftc --version Apple Swift version 5.2.4 (swiftlang-1103.0.32.9 clang-1103.0.32.53) Target: x86_64-apple-darwin19.6.0 % swiftc take1.swift % ./take1 Hello, world!このように実行できる。
% swiftc take2.swift % ./take2うお、ターミナルが終了してしまった。take2は実行されている。アクティビティモニタからtake2を探して、強制終了するしかない。
% swiftc take3.swift take3.swift:2:15: warning: expression of type 'NSApplication' is unused NSApplication.shared ~~~~~~~~~~~~~~^~~~~~ % ./take3こちらは普通に実行され、Ctrl-cで終了できる。
3. Swiftパッケージ
これまでは、1つのファイルにプログラム全てが書かれている形式だった。だが、通常のアプリケーションは、複数のファイルからなる。それは、Swiftパッケージと呼ばれる標準的な形式がある。
swift package init --type executableによって、ディレクトリが生成される。% cd ~/dev % mkdir HelloWorld % cd HelloWorld % swift package init --type executable Creating executable package: HelloWorld Creating Package.swift Creating README.md Creating .gitignore Creating Sources/ Creating Sources/HelloWorld/main.swift Creating Tests/ Creating Tests/LinuxMain.swift Creating Tests/HelloWorldTests/ Creating Tests/HelloWorldTests/HelloWorldTests.swift Creating Tests/HelloWorldTests/XCTestManifests.swiftこのように、現在いるディレクトリの名前を認識し、それを元にパッケージの名前を決定する。良いような気もするが、conventionで規定されるのはわかりにくい気もする。
Sources/HelloWorld/main.swiftprint("Hello, world!")と、最初からHello, world!と出力する内容がかかれている。
% swift build [3/3] Linking HelloWorld % swift run Hello, world!このように、コンパイルされ、実行される。
さきほどと同様に、
Sources/HelloWorld/main.swiftの内容を、take2.swiftと同じようにしてみよう。% swift run [3/3] Linking HelloWorld ^Cこちらは普通のCtrl-cで終了できた。
Sources/HelloWorld/main.swiftの内容を、take3.swiftと同じようにしてみよう。% swift run [3/3] Linking HelloWorld ^Cさきほどと同様にウィンドウが出て、ウィンドウを閉じると、アプリケーションも終了した。
done!
- 投稿日:2020-08-23T15:47:35+09:00
Swift + Chartsでグリッド線表示のカスタマイズ
目的
Chartsでグラフを描画する際、X軸のグリッド線の表示をカスタマイズしたい。
ライブラリ
任意のxAxisRendererをセットして描画をカスタマイズ
BarLineChartViewBaseのxAxisRendererプロパティには以下のコメントが書かれています。
/// The X axis renderer. This is a read-write property so you can set your own custom renderer here. /// **default**: An instance of XAxisRenderer @objc open lazy var xAxisRenderer = XAxisRenderer(viewPortHandler: _viewPortHandler, xAxis: _xAxis, transformer: _leftAxisTransformer)XAxisRendererクラスのrenderGridLinesファンクションがグリッド線表示を行う処理です。
これを継承したクラスでオーバーライドしてあげれば良いでしょう。例えば偶数のときだけ表示したいというような場合とします。
イメージ
デフォルト 偶数だけ → カスタムクラスの作成
swift:CustomXAxisRender.swift/// 呼び出し元で制御できるようプロトコルを作っておく protocol CustomXAxisRenderDelegate { func isRender(entry:Double) -> Bool } /// Optional化するためのエクステンション extension CustomXAxisRenderDelegate { func isRender(entry:Double) -> Bool{ return true } } /// レンダラーのカスタムクラス class CustomXAxisRender: XAxisRenderer { var renderDelegate:CustomXAxisRenderDelegate? override init(viewPortHandler: ViewPortHandler, xAxis: XAxis?, transformer: Transformer?) { super.init(viewPortHandler: viewPortHandler, xAxis: xAxis, transformer: transformer) } open override func renderGridLines(context: CGContext) { guard let xAxis = self.axis as? XAxis, let transformer = self.transformer else { return } if !xAxis.isDrawGridLinesEnabled || !xAxis.isEnabled { return } context.saveGState() defer { context.restoreGState() } context.clip(to: self.gridClippingRect) context.setShouldAntialias(xAxis.gridAntialiasEnabled) context.setStrokeColor(xAxis.gridColor.cgColor) context.setLineWidth(xAxis.gridLineWidth) context.setLineCap(xAxis.gridLineCap) if xAxis.gridLineDashLengths != nil { context.setLineDash(phase: xAxis.gridLineDashPhase, lengths: xAxis.gridLineDashLengths) } else { context.setLineDash(phase: 0.0, lengths: []) } let valueToPixelMatrix = transformer.valueToPixelMatrix var position = CGPoint(x: 0.0, y: 0.0) let entries = xAxis.entries for i in stride(from: 0, to: entries.count, by: 1) { /// 元の処理からの変更点はここだけ /// 引数にグラフメモリの値をセットしてあげる if let delegate = self.renderDelegate { /// entriesにグリッド線の値が入っている if !delegate.isRender(entry:entries[i]) { continue } } position.x = CGFloat(entries[i]) position.y = position.x position = position.applying(valueToPixelMatrix) drawGridLine(context: context, x: position.x, y: position.y) } } }XAxisRendererクラスにデリゲートを追加し、その戻り値で描画有無を制御しているだけです。
これを使うViewControllerとしてはViewController.swiftclass ViewController: UIViewController,CustomXAxisRenderDelegate { var chartView:LineChartView! override func viewDidLoad() { super.viewDidLoad() self.makeChart() } private func makeChart() { self.chartView = LineChartView(frame: self.view.frame) /// カスタムレンダラーをインスタンス化。コンストラクタの値はChartViewや元のXAxisのを再利用 var customRender = CustomXAxisRender(viewPortHandler: self.chartView.viewPortHandler, xAxis: self.chartView.xAxis, transformer: self.chartView.xAxisRenderer.transformer!) /// デリゲートをセット customRender.renderDelegate = self /// カスタムレンダラーをセットする self.chartView.xAxisRenderer = customRender let xAxis = self.chartView.xAxis xAxis.labelPosition = .bottom chartView.data = self.createChartsData() self.view.addSubview(chartView) } /// チャートデータのセットなど private func createChartsData() -> LineChartData { let bellValues = [120,109,86,214,487,64,87] let day = [1,2,3,4,5,6,7] var datas:[ChartDataEntry] = [] for i in 0..<day.count { datas.append(ChartDataEntry(x: Double(day[i]), y: Double(bellValues[i]))) } var lineChart = LineChartDataSet(entries: datas) return LineChartData(dataSets: [lineChart]) } /// グリッド線描画毎に呼ばれるデリゲート func isRender(entry:Double) -> Bool{ return Int(entry.truncatingRemainder(dividingBy: 2.0)) == 0 } }
- 投稿日:2020-08-23T14:02:24+09:00
Swiftで開発環境の情報を取得する
概要
- 下記の記事を読んで、開発環境を書こうというモチベーションが起きました。
- ただ毎回バージョンを確認するのが手間なので、自動で情報を取得できればと思いちょんプロを書きました。
- (皆さん何かしらの方法で既に楽してそうではあります)
- バッジの形式をカスタムしたい場合は
func badgeText(withLabel label: String, message: String, color: String = "brightgreen") -> Stringを変更してください。呼び出し例
guard let environmentInfo = EnvironmentInfoController.shared.getEnvironmentInfoString() else { return } print(environmentInfo)
- 出力結果
-brightgreen)  開発環境の取得クラス
- OSバージョンの取得
- プロジェクトのSwiftバージョンの取得
- How do I see which version of Swift I'm using?
- Stack Overflowの回答を参考にしましたがかなり力技です。
- プロジェクト毎にSwiftのバージョンは異なるので、
swift --versionのように一概に取得する手法は取れないですね。Xcode.appのバージョン取得import Foundation struct EnvironmentInfoController { static let shared = EnvironmentInfoController() func getEnvironmentInfoString() -> String? { guard let swiftVersion = getSwiftVersion() else { return nil } guard let osVersion = getOsVersion() else { return nil } guard let xcodeVersion = getXcodeVersion() else { return nil } let environmentText = String(format: "%@ %@ %@", badgeText(withLabel: "macOS", message: osVersion), badgeText(withLabel: "Swift", message: swiftVersion), badgeText(withLabel: "Xcode", message: xcodeVersion) ) /* // シンプルにテキストで表示したい場合 var environmentText = "" environmentText.append("OS: macOS \(osVersion)") environmentText.append("\nSwift: \(swiftVersion)") environmentText.append("\nXcode: \(xcodeVersion)") */ return environmentText } // MARK: - Helper Methods func badgeText(withLabel label: String, message: String, color: String = "brightgreen") -> String { return "-\(message)-\(color))" } // MARK: - Get EnvironmentInfo Methods private func getOsVersion() -> String? { let osVersion = ProcessInfo.processInfo.operatingSystemVersionString // e.g. "Version 10.15.4 (Build 19E287)" return osVersion // let osVersion = ProcessInfo.processInfo.operatingSystemVersion // return "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)" } private func getSwiftVersion() -> String? { #if swift(>=5.49) return "5.4.9" #elseif swift(>=5.48) return "5.4.8" #elseif swift(>=5.47) return "5.4.7" #elseif swift(>=5.46) return "5.4.6" #elseif swift(>=5.45) return "5.4.5" #elseif swift(>=5.44) return "5.4.4" #elseif swift(>=5.43) return "5.4.3" #elseif swift(>=5.42) return "5.4.2" #elseif swift(>=5.41) return "5.4.1" #elseif swift(>=5.40) return "5.4.0" #elseif swift(>=5.39) return "5.3.9" #elseif swift(>=5.38) return "5.3.8" #elseif swift(>=5.37) return "5.3.7" #elseif swift(>=5.36) return "5.3.6" #elseif swift(>=5.35) return "5.3.5" #elseif swift(>=5.34) return "5.3.4" #elseif swift(>=5.33) return "5.3.3" #elseif swift(>=5.32) return "5.3.2" #elseif swift(>=5.31) return "5.3.1" #elseif swift(>=5.30) return "5.3.0" #elseif swift(>=5.29) return "5.2.9" #elseif swift(>=5.28) return "5.2.8" #elseif swift(>=5.27) return "5.2.7" #elseif swift(>=5.26) return "5.2.6" #elseif swift(>=5.25) return "5.2.5" #elseif swift(>=5.24) return "5.2.4" #elseif swift(>=5.23) return "5.2.3" #elseif swift(>=5.22) return "5.2.2" #elseif swift(>=5.21) return "5.2.1" #elseif swift(>=5.20) return "5.2.0" #elseif swift(>=5.19) return "5.1.9" #elseif swift(>=5.18) return "5.1.8" #elseif swift(>=5.17) return "5.1.7" #elseif swift(>=5.16) return "5.1.6" #elseif swift(>=5.15) return "5.1.5" #elseif swift(>=5.14) return "5.1.4" #elseif swift(>=5.13) return "5.1.3" #elseif swift(>=5.12) return "5.1.2" #elseif swift(>=5.11) return "5.1.1" #elseif swift(>=5.10) return "5.1.0" #elseif swift(>=5.09) return "5.0.9" #elseif swift(>=5.08) return "5.0.8" #elseif swift(>=5.07) return "5.0.7" #elseif swift(>=5.06) return "5.0.6" #elseif swift(>=5.05) return "5.0.5" #elseif swift(>=5.04) return "5.0.4" #elseif swift(>=5.03) return "5.0.3" #elseif swift(>=5.02) return "5.0.2" #elseif swift(>=5.01) return "5.0.1" #elseif swift(>=5.00) return "5.0.0" #elseif swift(>=4.49) return "4.4.9" #elseif swift(>=4.48) return "4.4.8" #elseif swift(>=4.47) return "4.4.7" #elseif swift(>=4.46) return "4.4.6" #elseif swift(>=4.45) return "4.4.5" #elseif swift(>=4.44) return "4.4.4" #elseif swift(>=4.43) return "4.4.3" #elseif swift(>=4.42) return "4.4.2" #elseif swift(>=4.41) return "4.4.1" #elseif swift(>=4.40) return "4.4.0" #elseif swift(>=4.39) return "4.3.9" #elseif swift(>=4.38) return "4.3.8" #elseif swift(>=4.37) return "4.3.7" #elseif swift(>=4.36) return "4.3.6" #elseif swift(>=4.35) return "4.3.5" #elseif swift(>=4.34) return "4.3.4" #elseif swift(>=4.33) return "4.3.3" #elseif swift(>=4.32) return "4.3.2" #elseif swift(>=4.31) return "4.3.1" #elseif swift(>=4.30) return "4.3.0" #elseif swift(>=4.29) return "4.2.9" #elseif swift(>=4.28) return "4.2.8" #elseif swift(>=4.27) return "4.2.7" #elseif swift(>=4.26) return "4.2.6" #elseif swift(>=4.25) return "4.2.5" #elseif swift(>=4.24) return "4.2.4" #elseif swift(>=4.23) return "4.2.3" #elseif swift(>=4.22) return "4.2.2" #elseif swift(>=4.21) return "4.2.1" #elseif swift(>=4.20) return "4.2.0" #elseif swift(>=4.19) return "4.1.9" #elseif swift(>=4.18) return "4.1.8" #elseif swift(>=4.17) return "4.1.7" #elseif swift(>=4.16) return "4.1.6" #elseif swift(>=4.15) return "4.1.5" #elseif swift(>=4.14) return "4.1.4" #elseif swift(>=4.13) return "4.1.3" #elseif swift(>=4.12) return "4.1.2" #elseif swift(>=4.11) return "4.1.1" #elseif swift(>=4.10) return "4.1.0" #elseif swift(>=4.09) return "4.0.9" #elseif swift(>=4.08) return "4.0.8" #elseif swift(>=4.07) return "4.0.7" #elseif swift(>=4.06) return "4.0.6" #elseif swift(>=4.05) return "4.0.5" #elseif swift(>=4.04) return "4.0.4" #elseif swift(>=4.03) return "4.0.3" #elseif swift(>=4.02) return "4.0.2" #elseif swift(>=4.01) return "4.0.1" #elseif swift(>=4.00) return "4.0.0" #elseif swift(>=3.49) return "3.4.9" #elseif swift(>=3.48) return "3.4.8" #elseif swift(>=3.47) return "3.4.7" #elseif swift(>=3.46) return "3.4.6" #elseif swift(>=3.45) return "3.4.5" #elseif swift(>=3.44) return "3.4.4" #elseif swift(>=3.43) return "3.4.3" #elseif swift(>=3.42) return "3.4.2" #elseif swift(>=3.41) return "3.4.1" #elseif swift(>=3.40) return "3.4.0" #elseif swift(>=3.39) return "3.3.9" #elseif swift(>=3.38) return "3.3.8" #elseif swift(>=3.37) return "3.3.7" #elseif swift(>=3.36) return "3.3.6" #elseif swift(>=3.35) return "3.3.5" #elseif swift(>=3.34) return "3.3.4" #elseif swift(>=3.33) return "3.3.3" #elseif swift(>=3.32) return "3.3.2" #elseif swift(>=3.31) return "3.3.1" #elseif swift(>=3.30) return "3.3.0" #elseif swift(>=3.29) return "3.2.9" #elseif swift(>=3.28) return "3.2.8" #elseif swift(>=3.27) return "3.2.7" #elseif swift(>=3.26) return "3.2.6" #elseif swift(>=3.25) return "3.2.5" #elseif swift(>=3.24) return "3.2.4" #elseif swift(>=3.23) return "3.2.3" #elseif swift(>=3.22) return "3.2.2" #elseif swift(>=3.21) return "3.2.1" #elseif swift(>=3.20) return "3.2.0" #elseif swift(>=3.19) return "3.1.9" #elseif swift(>=3.18) return "3.1.8" #elseif swift(>=3.17) return "3.1.7" #elseif swift(>=3.16) return "3.1.6" #elseif swift(>=3.15) return "3.1.5" #elseif swift(>=3.14) return "3.1.4" #elseif swift(>=3.13) return "3.1.3" #elseif swift(>=3.12) return "3.1.2" #elseif swift(>=3.11) return "3.1.1" #elseif swift(>=3.10) return "3.1.0" #elseif swift(>=3.09) return "3.0.9" #elseif swift(>=3.08) return "3.0.8" #elseif swift(>=3.07) return "3.0.7" #elseif swift(>=3.06) return "3.0.6" #elseif swift(>=3.05) return "3.0.5" #elseif swift(>=3.04) return "3.0.4" #elseif swift(>=3.03) return "3.0.3" #elseif swift(>=3.02) return "3.0.2" #elseif swift(>=3.01) return "3.0.1" #elseif swift(>=3.00) return "3.0.0" #elseif swift(>=2.49) return "2.4.9" #elseif swift(>=2.48) return "2.4.8" #elseif swift(>=2.47) return "2.4.7" #elseif swift(>=2.46) return "2.4.6" #elseif swift(>=2.45) return "2.4.5" #elseif swift(>=2.44) return "2.4.4" #elseif swift(>=2.43) return "2.4.3" #elseif swift(>=2.42) return "2.4.2" #elseif swift(>=2.41) return "2.4.1" #elseif swift(>=2.40) return "2.4.0" #elseif swift(>=2.39) return "2.3.9" #elseif swift(>=2.38) return "2.3.8" #elseif swift(>=2.37) return "2.3.7" #elseif swift(>=2.36) return "2.3.6" #elseif swift(>=2.35) return "2.3.5" #elseif swift(>=2.34) return "2.3.4" #elseif swift(>=2.33) return "2.3.3" #elseif swift(>=2.32) return "2.3.2" #elseif swift(>=2.31) return "2.3.1" #elseif swift(>=2.30) return "2.3.0" #elseif swift(>=2.29) return "2.2.9" #elseif swift(>=2.28) return "2.2.8" #elseif swift(>=2.27) return "2.2.7" #elseif swift(>=2.26) return "2.2.6" #elseif swift(>=2.25) return "2.2.5" #elseif swift(>=2.24) return "2.2.4" #elseif swift(>=2.23) return "2.2.3" #elseif swift(>=2.22) return "2.2.2" #elseif swift(>=2.21) return "2.2.1" #elseif swift(>=2.20) return "2.2.0" #elseif swift(>=2.19) return "2.1.9" #elseif swift(>=2.18) return "2.1.8" #elseif swift(>=2.17) return "2.1.7" #elseif swift(>=2.16) return "2.1.6" #elseif swift(>=2.15) return "2.1.5" #elseif swift(>=2.14) return "2.1.4" #elseif swift(>=2.13) return "2.1.3" #elseif swift(>=2.12) return "2.1.2" #elseif swift(>=2.11) return "2.1.1" #elseif swift(>=2.10) return "2.1.0" #elseif swift(>=2.09) return "2.0.9" #elseif swift(>=2.08) return "2.0.8" #elseif swift(>=2.07) return "2.0.7" #elseif swift(>=2.06) return "2.0.6" #elseif swift(>=2.05) return "2.0.5" #elseif swift(>=2.04) return "2.0.4" #elseif swift(>=2.03) return "2.0.3" #elseif swift(>=2.02) return "2.0.2" #elseif swift(>=2.01) return "2.0.1" #elseif swift(>=2.00) return "2.0.0" #elseif swift(>=1.49) return "1.4.9" #elseif swift(>=1.48) return "1.4.8" #elseif swift(>=1.47) return "1.4.7" #elseif swift(>=1.46) return "1.4.6" #elseif swift(>=1.45) return "1.4.5" #elseif swift(>=1.44) return "1.4.4" #elseif swift(>=1.43) return "1.4.3" #elseif swift(>=1.42) return "1.4.2" #elseif swift(>=1.41) return "1.4.1" #elseif swift(>=1.40) return "1.4.0" #elseif swift(>=1.39) return "1.3.9" #elseif swift(>=1.38) return "1.3.8" #elseif swift(>=1.37) return "1.3.7" #elseif swift(>=1.36) return "1.3.6" #elseif swift(>=1.35) return "1.3.5" #elseif swift(>=1.34) return "1.3.4" #elseif swift(>=1.33) return "1.3.3" #elseif swift(>=1.32) return "1.3.2" #elseif swift(>=1.31) return "1.3.1" #elseif swift(>=1.30) return "1.3.0" #elseif swift(>=1.29) return "1.2.9" #elseif swift(>=1.28) return "1.2.8" #elseif swift(>=1.27) return "1.2.7" #elseif swift(>=1.26) return "1.2.6" #elseif swift(>=1.25) return "1.2.5" #elseif swift(>=1.24) return "1.2.4" #elseif swift(>=1.23) return "1.2.3" #elseif swift(>=1.22) return "1.2.2" #elseif swift(>=1.21) return "1.2.1" #elseif swift(>=1.20) return "1.2.0" #elseif swift(>=1.19) return "1.1.9" #elseif swift(>=1.18) return "1.1.8" #elseif swift(>=1.17) return "1.1.7" #elseif swift(>=1.16) return "1.1.6" #elseif swift(>=1.15) return "1.1.5" #elseif swift(>=1.14) return "1.1.4" #elseif swift(>=1.13) return "1.1.3" #elseif swift(>=1.12) return "1.1.2" #elseif swift(>=1.11) return "1.1.1" #elseif swift(>=1.10) return "1.1.0" #elseif swift(>=1.09) return "1.0.9" #elseif swift(>=1.08) return "1.0.8" #elseif swift(>=1.07) return "1.0.7" #elseif swift(>=1.06) return "1.0.6" #elseif swift(>=1.05) return "1.0.5" #elseif swift(>=1.04) return "1.0.4" #elseif swift(>=1.03) return "1.0.3" #elseif swift(>=1.02) return "1.0.2" #elseif swift(>=1.01) return "1.0.1" #elseif swift(>=1.00) return "1.0.0" #endif return nil } private func getXcodeVersion() -> String? { guard let xcode = Bundle(path: "/Applications/Xcode.app") else { return nil } guard let xcodeVersion = xcode.infoDictionary?["CFBundleShortVersionString"] as? String else { return nil } return xcodeVersion } // MARK: - DEBUG Methods private func printAllSwiftVersion() -> Double? { for majorVerion in (1...5).reversed() { for minorVersion in (0..<5).reversed() { for buildVersion in (0..<10).reversed() { print("#elseif swift(>=\(majorVerion).\(minorVersion)\(buildVersion))") print("return \"\(majorVerion).\(minorVersion).\(buildVersion)\"") } } } return nil } }その他
- 外部ライブラリを使用していてそのバージョンを取得したい場合は、下記のライブラリのロジックを見れば参考になるでしょうか。(詳しく見れてないです)
- 投稿日:2020-08-23T13:12:37+09:00
【Swift】Cloud FirestoreのドキュメントにcreatedAtとupdatedAtを登録する実装
はじめに
createdAtとupdatedAtはRDBだと自動的に生成してくれますが、Cloud Firestoreでは基本的にはクライアントからデータを登録する必要があります。
しかし、クライアント側で生成した日付データは改竄可能なので、Firestoreに適切ではない日付が登録される可能性があります。
そこでFirestore側には、ドキュメントがFirestoreに登録された時点での日付を設定できる関数が用意されています。
今回はその関数を使って、createdAtとupdatedAtを実装したいと思います。ドキュメントの定義
まずはcreatedAtとupdatedAtを付与するドキュメントを下記のように定義しました。
SampleData.swiftimport Firebase struct SampleData: Codable { internal let createdAt: Timestamp? = nil internal let updatedAt: Timestamp? = nil }このSampleDataをFirestoreに登録した時に、createdAtとupdatedAtに登録した日付が保存されていることがゴールとなります。
Firestoreに登録された日付をcreatedAtとupdatedAtに設定する
Firestoreに登録された日付を設定するにはFieldValue.serverTimestamp関数を使用します。
この関数をメンバに持たせておくことで、そのデータがFirestoreに保存されたタイミングで、日付を自動的に設定してくれるというものです。
具体的には下記のように実装します。SampleData.swiftstruct SampleData: Codable { internal let createdAt: Timestamp? = nil internal let updatedAt: Timestamp? = nil // ① enum CodingKeys: String, CodingKey { case createdAt case updatedAt } } extension SampleData { func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) // ② if self.createdAt == nil { try container.encode(FieldValue.serverTimestamp(), forKey: .createdAt) } // ③ try container.encode(FieldValue.serverTimestamp(), forKey: .updatedAt) } }①では、SampleDataをEncode&Decodeする対象のメンバを指定しています。
②のcreatedAtでは、登録された日付を保持したいので、登録時のみ(つまり初期値nilの場合)設定するようにしています。
③のupdateAtでは、更新されるたびに日付を上書きしたいので常にFieldValue.serverTimestamp()を設定します。SampleDataを新規で登録する際はcreatedAtとupdatedAt両方に日付データが保存され、更新時にはupdatedAtだけ更新されるようになります。
EntityをEncode&Decodeする実装はこちらの記事で解説しています。
- 投稿日:2020-08-23T13:05:12+09:00
[初心者向け]UITableViewでCellのハイライトをゆっくりと消す方法(画面遷移した先から戻ってきたタイミングで)
はじめに
・Swift5,Xcode11.6を使用しています。
できること
・UITableView上でCellを選択したときにゆっくりとハイライトさせる
・画面遷移先から戻ってきたときに、ゆっくりとハイライトを終わらせるswiftoverride func viewWillAppear(_ animated: Bool) { navigationController?.isNavigationBarHidden = true //ゆっくり付けたり消したりする if let indexPathForSelectedRow = tableView.indexPathForSelectedRow { tableView.deselectRow(at: indexPathForSelectedRow, animated: true) } }結果
- 投稿日:2020-08-23T12:26:14+09:00
ObservableのGenericsにAssociated Values付きEnumを利用したExtensionを作る
結論
- enumにアクセス用のProtocolを適用することで可能.
検討したきっかけ
- Rxで反復して行なっているAssociated Values付きEnumへの処理を共通化したかった.
- 新メンバーのイニシャルコスト.(説明する労力、理解してもらう労力)
- 実装コスト.(数行の労力、レビューの労力)
実践
サンプル: Result型
ケース: SuccessとFailureをそれぞれに分けるオペレータを作る.問題: Elementの型条件にResultにしたときのコンパイルエラー。
- ResultのGenericsの指定が必要.
- 具体的な型を指定すると旨味が少ない.
解決方法:
ResultTypeというアクセス用プロトコルを定義しenum適用する.以下サンプルコード.
ObservableType+Extension.swiftextension ObservableType where Element: ResultType { func mapToSuccess() -> Observable<Element.SuccessType> { compactMap { $0.success } } func mapToFailure() -> Observable<Element.FailureType> { compactMap { $0.failure } } }ResultType.swiftprotocol ResultType { associatedtype SuccessType associatedtype FailureType: Error var success: SuccessType? { get } var failure: FailureType? { get } }Result+Extension.swiftextension Result: ResultType { typealias SuccessType = Success typealias FailureType = Failure var success: Success? { switch self { case .success(let success): return success default: return nil } } var failure: Failure? { switch self { case .failure(let error): return error default: return nil } } }おまけ
Successをさらに操作する定義など.
Barcodeよりもう少し抽象的なenumであれば実用的かも.Omake.swiftenum Barcode { case qr(String) case upc(Int, Int, Int, Int) var qrValue: String? { switch self { case .qr(let code): return code default: return nil } } var upcValue: (Int, Int, Int, Int)? { switch self { case .upc(let digit1, let digit2, let digit3, let digit4): return (digit1, digit2, digit3, digit4) default: return nil } } } extension ObservableType where Element: ResultType, Element.SuccessType == Barcode { func mapToQrCode() -> Observable<String> { compactMap { $0.success?.qrValue } } func mapToUpc() -> Observable<(digit1: Int, digit2: Int, digit3: Int, digit4: Int)> { compactMap { $0.success?.upcValue } } }
- 投稿日:2020-08-23T01:11:37+09:00
【入門】iOS アプリ開発 #6【キャラクタの操作】
はじめに
今回はパックマンのキャラクタ操作を作成する。操作はスワイプで行い、以下が完成イメージ。ソースコードはGitHub に公開しているので参照してほしい。
仕様書
注目すべきところはスピードレベルの仕様。単に1ドットずつ動かすなら簡単だが、微妙なスピード調整の仕様があり、ゲームの難易度を調整している。
エサを食べる、食べない、パワーエサを食べた状態などで、移動スピードが異なる。仕様段階でここまで定義していたとは奥深い。
「スピードの数字は、1フレームに1ドット移動するスピードを”16”として、2ドット移動を”32”として、他はそれに準じて分割。」と書いてあるので、1ドット進むスピードを 16 として、作成していく。
また仕様書には記載がないが、パックマンが迷路を曲がる時はモンスターより早く内側に曲り、曲りながら移動することでモンスターから引き離せるチューニングがされているようだ。本当に奥深い。
スワイプ操作の作成
スワイプ操作をパックマン・オブジェクトに伝えるのは、
sendEvent(message: .Swipe, parameter: [direction])というようにしたい。
イベントは、迷路シーンの中で生成したパックマン・オブジェクトへ通知する。
大もととなる GameScene クラスの touchesBegan, touchesEnded メソッドにスワイプ動作のコードを実装する。gameMain オブジェクトの sendEvent を呼ぶ。
class GameScene: SKScene { /// Main object with main game sequence private var gameMain: CgGameMain! /// Points for Swipe operation private var startPoint: CGPoint = CGPoint.init() private var endPoint: CGPoint = CGPoint.init() override func didMove(to view: SKView) { // Create and start game sequence. gameMain = CgGameMain(skscene: self) gameMain.startSequence() } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { // Get start touchpoint for swipe. if let t = touches.first { let location = t.location(in: self) startPoint = CGPoint(x: location.x, y: location.y) } } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { // Get end touchpoint for swipe. if let t = touches.first { let location = t.location(in: self) endPoint = CGPoint(x: location.x, y: location.y) let x_diff = endPoint.x - startPoint.x let y_diff = endPoint.y - startPoint.y // Send swipe message to GameMain. if abs(x_diff) > abs(y_diff) { gameMain.sendEvent(message: .Swipe, parameter: [Int(x_diff > 0 ? EnDirection.Right.rawValue : EnDirection.Left.rawValue)]) } else { gameMain.sendEvent(message: .Swipe, parameter: [Int(y_diff > 0 ? EnDirection.Up.rawValue : EnDirection.Down.rawValue)]) } } }CgGameMain class
gameMain: CgGameMain (オブジェクト:クラス)は、前回のアーキテクチャでいう、root のオブジェクトとして作成した。
gameMain の sendEvent メソッドは、gameMain が持つアクティブ(enabled=true)なオブジェクトにメッセージを送信する。
以下のコードにより、scene_maze(迷路を描画するシーン) が startSequence() により enabled=true となり該当する。
/// Main sequence of game scene. class CgGameMain : CgSceneFrame { private var scene_attractMode: CgSceneAttractMode! private var scene_maze: CgSceneMaze! private var scene_intermission1: CgSceneIntermission1! private var scene_intermission2: CgSceneIntermission2! private var scene_intermission3: CgSceneIntermission3! init(skscene: SKScene) { super.init() // Create SpriteKit managers. self.sprite = CgSpriteManager(view: skscene, imageNamed: "pacman16_16.png", width: 16, height: 16, maxNumber: 64) self.background = CgCustomBackgroundManager(view: skscene, imageNamed: "pacman8_8.png", width: 8, height: 8, maxNumber: 2) self.sound = CgSoundManager(binding: self, view: skscene) self.context = CgContext() scene_attractMode = CgSceneAttractMode(object: self) scene_maze = CgSceneMaze(object: self) scene_intermission1 = CgSceneIntermission1(object: self) scene_intermission2 = CgSceneIntermission2(object: self) scene_intermission3 = CgSceneIntermission3(object: self) } /// Handle sequence /// To override in a derived class. /// - Parameter sequence: Sequence number /// - Returns: If true, continue the sequence, if not, end the sequence. override func handleSequence(sequence: Int) -> Bool { switch sequence { case 0: // Start maze sequence. scene_maze.startSequence() goToNextSequence() default: // Forever loop break } // Continue running sequence. return true } }迷路シーンクラスでは、初期化時に新規に作成したパックマンのクラス CgPlayer を CgSceneMaze に関連付け(binding)て生成し、イベントが通知できるようにする。
そのため、CgPlayerクラスの基底クラスは CbObject となっている。
シーケンス実装の handleSequenceメソッド内でパックマンを player.start()
(enabled=true)にすると、CgPlayer内の updateメソッドがフレーム毎に呼ばれて移動処理を更新する仕組みとなる。class CgSceneMaze: CgSceneFrame, ActorDeligate { var player : CgPlayer! convenience init(object: CgSceneFrame) { self.init(binding: object, context: object.context, sprite: object.sprite, background: object.background, sound: object.sound) player = CgPlayer(binding: self, deligateActor: self) } /// Handle sequence /// To override in a derived class. /// - Parameter sequence: Sequence number /// - Returns: If true, continue the sequence, if not, end the sequence. override func handleSequence(sequence: Int) -> Bool { switch sequence { case 0: drawBackground() let _ = setAndDraw() printPlayers() printBlinking1Up() drawPowerFeed(state: .Blinking) player.reset() player.start() goToNextSequence() case 1: // Foever loop break // // Round clear animation(Maze flashes) // case 10: blinkingTimer = 104 // 104*16ms = 1664ms goToNextSequence() case 11: if blinkingTimer == 0 { goToNextSequence() } else { let remain = blinkingTimer % 26 if remain == 0 { drawMazeWall(color: .White) } else if remain == 13 { // 13*16ms = 208ms drawMazeWall(color: .Blue) } blinkingTimer -= 1 } case 12: // Stop and exit running sequence. return false default: // Stop and exit running sequence. return false } // Play BGM if player.timer_playerWithPower.isCounting() { sound.playBGM(.BgmPower) } else { sound.playBGM(.BgmNormal) } // Continue running sequence. return true }CgPlayer class
パックマンの CgPlayerクラスは、CbContainer & CbObjectクラスを継承した CgActorクラスを継承している。handleEventメソッドをオーバーライドすれば、Swipe イベントを取得できる。これがやりたいためにクラス構成を設計してきた。
/// Player(Pacman) class derived from CgAcotr class CgPlayer : CgActor { enum EnPlayerAction: Int { case None, Stopping, Walking, Turning, EatingDot, EatingPower, EatingFruit } var targetDirecition: EnDirection = .Stop var actionState: EnPlayerAction = .None var timer_playerWithPower: CbTimer! var timer_playerNotToEat: CbTimer! override init(binding object: CgSceneFrame, deligateActor: ActorDeligate) { super.init(binding: object, deligateActor: deligateActor) timer_playerWithPower = CbTimer(binding: self) timer_playerNotToEat = CbTimer(binding: self) actor = .Pacman sprite_number = actor.getSpriteNumber() enabled = false } // ============================================================ // Event Handler // ============================================================ /// Event handler /// - Parameters: /// - sender: Message sender /// - id: Message ID /// - values: Parameters of message override func handleEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) { switch message { case .Swipe: if let direction = EnDirection(rawValue: values[0]) { targetDirecition = direction } default: break } } /// Update handler /// - Parameter interval: Interval time(ms) to update override func update(interval: Int) { if actionState == .Turning { turn() } else { if canMove(to: targetDirecition) { direction.set(to: targetDirecition) } else { direction.update() if canTurn() { actionState = .Turning direction.set(to: targetDirecition) return } } move() } }handleEventメソッドでパックマンの移動方向を設定し、updateメソッドで設定された移動方向の処理を行う。
まとめ
今回、パックマンのキャラクタ操作を実装するにあたり、以下のファイル(クラス)を新規で作成した。
GameMain.swift(CgGameMain)
GamePlayer.swift(CgPlayer)
GameActor.swift(CgActor,CgPosition,CgDirection)
GameContext.swift(CbContext)ソースコードは全体で 3000行程度。
操作のパフォーマンスは問題ない状況。次はモンスターを作成していく。





