20200823のSwiftに関する記事は8件です。

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を使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
MLBoysチャンネル
Medium

相棒
note

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最小限のSwiftプログラムを作ってみる

通常、macOSのプログラムを開発するには、Xcodeを使う。プロジェクトを新規作成すると、テンプレートによってアプリケーションの雛型が作成される。
が、このような雛型は、その中で何をしているのかよくわからないという欠点がある。そこで、最小限のSwiftプログラムを書いてみる。

以下を参考にした。
https://gist.github.com/habemus-papadum/11f069b6d074f4bca794bf9399432533

1. Swiftインタープリタ

まず、以下のファイルを作成する。

take1.swift
print("Hello, world!")

これも有効なSwiftプログラムである。コマンドラインから実行してみる。

% swift take1.swift 
Hello, world!

次に、ウィンドウを表示してみる。

take2.swift
import Cocoa
let window = NSWindow(contentRect: NSMakeRect(0, 0, 320, 200), styleMask: .titled, backing: .buffered, defer: true)
window.orderFrontRegardless()
NSApp.run()

左下に黒いウィンドウが表示されたはずである。これが最小限のウィンドウを表示するプログラムではないか。

次に、もう少しCocoaアプリっぽいプログラムにしてみる。

take3.swift
import 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.swift
print("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!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift + Chartsでグリッド線表示のカスタマイズ

目的

Chartsでグラフを描画する際、X軸のグリッド線の表示をカスタマイズしたい。

ライブラリ

danielgindi/Charts

任意の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.swift
class 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
    }

}

サンプルソースはこちらに上げています

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftで開発環境の情報を取得する

概要

  • 下記の記事を読んで、開発環境を書こうというモチベーションが起きました。
  • ただ毎回バージョンを確認するのが手間なので、自動で情報を取得できればと思いちょんプロを書きました。
    • (皆さん何かしらの方法で既に楽してそうではあります)
  • バッジの形式をカスタムしたい場合はfunc badgeText(withLabel label: String, message: String, color: String = "brightgreen") -> Stringを変更してください。

呼び出し例

guard let environmentInfo = EnvironmentInfoController.shared.getEnvironmentInfoString() else { return }
print(environmentInfo)
  • 出力結果

macOS: Version 10.15.4 (Build 19E287) Swift: 5.0.2 Xcode: 11.6

![macOS: Version 10.15.4 (Build 19E287)](https://img.shields.io/badge/macOS-Version 10.15.4 (Build 19E287)-brightgreen) ![Swift: 5.0.2](https://img.shields.io/badge/Swift-5.0.2-brightgreen) ![Xcode: 11.6](https://img.shields.io/badge/Xcode-11.6-brightgreen)

開発環境の取得クラス

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 "![\(label): \(message)](https://img.shields.io/badge/\(label)-\(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
    }
}

その他

  • 外部ライブラリを使用していてそのバージョンを取得したい場合は、下記のライブラリのロジックを見れば参考になるでしょうか。(詳しく見れてないです)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Cloud FirestoreのドキュメントにcreatedAtとupdatedAtを登録する実装

はじめに

createdAtとupdatedAtはRDBだと自動的に生成してくれますが、Cloud Firestoreでは基本的にはクライアントからデータを登録する必要があります。
しかし、クライアント側で生成した日付データは改竄可能なので、Firestoreに適切ではない日付が登録される可能性があります。
そこでFirestore側には、ドキュメントがFirestoreに登録された時点での日付を設定できる関数が用意されています。
今回はその関数を使って、createdAtとupdatedAtを実装したいと思います。

ドキュメントの定義

まずはcreatedAtとupdatedAtを付与するドキュメントを下記のように定義しました。

SampleData.swift
import 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.swift
struct 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する実装はこちらの記事で解説しています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初心者向け]UITableViewでCellのハイライトをゆっくりと消す方法(画面遷移した先から戻ってきたタイミングで)

はじめに

・Swift5,Xcode11.6を使用しています。

できること

・UITableView上でCellを選択したときにゆっくりとハイライトさせる
・画面遷移先から戻ってきたときに、ゆっくりとハイライトを終わらせる

swift
 override func viewWillAppear(_ animated: Bool) {
        navigationController?.isNavigationBarHidden = true

        //ゆっくり付けたり消したりする
        if let indexPathForSelectedRow = tableView.indexPathForSelectedRow {
            tableView.deselectRow(at: indexPathForSelectedRow, animated: true)
        }
    }

結果

以下のような効果のハイライトが実装できます。
N0PFF6c49xCIBZmrkbWF1598155405-1598155417.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ObservableのGenericsにAssociated Values付きEnumを利用したExtensionを作る

結論

  • enumにアクセス用のProtocolを適用することで可能.

検討したきっかけ

  • Rxで反復して行なっているAssociated Values付きEnumへの処理を共通化したかった.
    • 新メンバーのイニシャルコスト.(説明する労力、理解してもらう労力)
    • 実装コスト.(数行の労力、レビューの労力)

実践

サンプル: Result型
ケース: SuccessとFailureをそれぞれに分けるオペレータを作る.

問題: Elementの型条件にResultにしたときのコンパイルエラー。

  • ResultのGenericsの指定が必要.
  • 具体的な型を指定すると旨味が少ない.

解決方法:
ResultTypeというアクセス用プロトコルを定義しenum適用する.

以下サンプルコード.

ObservableType+Extension.swift
extension ObservableType where Element: ResultType {
    func mapToSuccess() -> Observable<Element.SuccessType> {
        compactMap { $0.success }
    }

    func mapToFailure() -> Observable<Element.FailureType> {
        compactMap { $0.failure }
    }
}
ResultType.swift
protocol ResultType {
    associatedtype SuccessType
    associatedtype FailureType: Error

    var success: SuccessType? { get }
    var failure: FailureType? { get }
}
Result+Extension.swift
extension 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.swift
enum 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 }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【入門】iOS アプリ開発 #6【キャラクタの操作】

はじめに

今回はパックマンのキャラクタ操作を作成する。操作はスワイプで行い、以下が完成イメージ。ソースコードはGitHub に公開しているので参照してほしい。

※YouTube動画
IMAGE ALT TEXT HERE

仕様書

注目すべきところはスピードレベルの仕様。単に1ドットずつ動かすなら簡単だが、微妙なスピード調整の仕様があり、ゲームの難易度を調整している。

Spec1.png

エサを食べる、食べない、パワーエサを食べた状態などで、移動スピードが異なる。仕様段階でここまで定義していたとは奥深い。

Spec3.png

「スピードの数字は、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行程度。
操作のパフォーマンスは問題ない状況。

次はモンスターを作成していく。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む