20210510のSwiftに関する記事は26件です。

XCTUnwrapを使ってUnitTestのunwrapをすっきり書く

概要 Xcode11以降に追加されたXCTUnwrapを使ってテストコード中のOptional値のunwrapをスッキリ書く方法の紹介です。 XCTUnwrapとは 公式ドキュメントには以下のような記載があります https://developer.apple.com/documentation/xctest/3380195-xctunwrap Asserts that an expression is not nil, and returns the unwrapped value. ざっくり言えば「unwrapとXCTAssertNotNil」という感じです。 どんなときに有効か unwrapしたオブジェクトを計算に使いたいケースで有効。 unwrapしたオブジェクトが「nilであるかどうか」をテストしたいケースには向かない。 以下に具体例。 一般的なUnitTest中のunwrapの書き方(XCTUnwrapを使わない書き方) func testSample() { let string = "1" // 「if let」だとネストが深くなるので「guard let」で書くのが一般的かと思います guard var number = Int(string) else { XCTFail() // unwrapできないときはテストを失敗させる return // 以降の処理を行わせないためにreturn } number += 1 XCTAssertEqual(unwrappedNumber, 2) } XCTUnwrapを使った書き方 func testSample() throws { // tryで例外を投げる可能性があるのでthrowsが必要 let string = "1" let number = Int(string) var unwrappedNumber = try XCTUnwrap(number) // unwrapに失敗したらthrowされるので以降の処理とテストは実施されない // testSampleのテストはfailureとして処理される unwrappedNumber += 1 XCTAssertEqual(unwrappedNumber, 2) } XCTUnwrapを使った書き方の特徴 unwrapできないとき例外を投げてテスト失敗になるので、unwrap出来なかったケースを考えなくて済む 「一般的な書き方」のguard ~ elseのスコープで行っている「XCTFailとreturn」など 一行で済むのでコードが簡潔になる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift5] Charts の折れ線グラフをカスタマイズする

個人開発のiOSアプリでChartsの折れ線グラフについて調べたことをまとめました。 iOS Chartsとは 様々な形のグラフを生成できるSwiftライブラリ。折れ線グラフ以外にも棒グラフ、パイチャートなど種類とデザインが豊富。 インストール sudo gem install cocoapods pod setup cd /Users/XXX/your_xcode_directory pod init vim Podfile --------------------- # Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'XXXXX' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for XXXXX # ここに追加したいライブラリを設定 pod 'Charts' end --------------------- pod install Chartsの利用例 ・import Charts する ・StoryBoardを使う場合は、UIViewをセットしてLineChartViewのクラスとしてコードと繋ぐ ・LineChartDataSetにデータ要素(entries)、各種設定を追加 ・調整したDataSetをLineChartViewに追加 ・LineChartViewへグラフの設定をして完成 import UIKit import Charts class StatsViewController: UIViewController { @IBOutlet weak var lineChartView: LineChartView! // モック用データ let rawDataGraph: [Int] = [130, 240, 500, 550, 670, 800, 950, 1300, 1400, 1500, 1700, 2100, 2500, 3600, 4200, 4300, 4700, 4800, 5400, 5800, 5900, 6700] override func viewDidLoad() { super.viewDidLoad() // Chart dataSet準備 let entries = rawDataGraph.enumerated().map { ChartDataEntry(x: Double($0.offset), y: Double($0.element)) } let dataSet = LineChartDataSet(entries: entries) // 線の太さ dataSet.lineWidth = 5 // 各プロットのラベル表示 dataSet.drawValuesEnabled = false // 各プロットの丸表示 dataSet.drawCirclesEnabled = true // 各プロットの丸の大きさ dataSet.circleRadius = 2 // 各プロットの丸の色 dataSet.circleColors = [UIColor.lightGray] // 作成したデータセットをLineChartViewに追加 lineChartView.data = LineChartData(dataSet: dataSet) // X軸のラベルの位置を下に設定 lineChartView.xAxis.labelPosition = .bottom // X軸のラベルの色を設定 lineChartView.xAxis.labelTextColor = .systemGray // X軸の線、グリッドを非表示にする lineChartView.xAxis.drawGridLinesEnabled = false lineChartView.xAxis.drawAxisLineEnabled = false // 右側のY座標軸は非表示にする lineChartView.rightAxis.enabled = false // Y座標の値が0始まりになるように設定 lineChartView.leftAxis.axisMinimum = 0.0 lineChartView.leftAxis.axisMaximum = 10000.0 lineChartView.leftAxis.drawZeroLineEnabled = true lineChartView.leftAxis.zeroLineColor = .systemGray // グラフに境界線(横)を追加 let limitLine = ChartLimitLine(limit: 7200, label: "AAAAA") limitLine.lineColor = .darkGray limitLine.valueTextColor = .darkGray lineChartView.leftAxis.addLimitLine(limitLine) // グラフに境界線(縦)を追加 let limitLineX = ChartLimitLine(limit: 3, label: "BBBBB") limitLineX.lineColor = .darkGray limitLineX.valueTextColor = .darkGray lineChartView.xAxis.addLimitLine(limitLineX) // ラベルの数を設定 lineChartView.leftAxis.labelCount = 5 // ラベルの色を設定 lineChartView.leftAxis.labelTextColor = .systemGray // グリッドの色を設定 lineChartView.leftAxis.gridColor = .systemGray // 軸線は非表示にする lineChartView.leftAxis.drawAxisLineEnabled = false // 凡例を非表示 lineChartView.legend.enabled = false // タップでプロットを選択できないようにする lineChartView.highlightPerTapEnabled = false // ピンチズームオフ lineChartView.pinchZoomEnabled = false // ダブルタップズームオフ lineChartView.doubleTapToZoomEnabled = false // アニメーションをつける lineChartView.animate(xAxisDuration: 1.0, yAxisDuration: 1.3, easingOption: .easeInCubic) } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像のヒストグラムを作る(2) (モノクローム画像編)

画像のヒストグラムを作る(2) (グレイスケール) 昨日の続きです. 昨日使用したvImageHistogramCalculation_ARGB8888ですが,Appleのレファレンスを参照すると,Planar8用のメソッドがあったので,今日はこれを使用してグレイスケール画像のヒストグラムを作成します. vImageHistogramCalculation_Planar8(::_:) Calculates the histogram of a Planar8 image. func vImageHistogramCalculation_Planar8(_ src: UnsafePointer<vImage_Buffer>, _ histogram: UnsafeMutablePointer<vImagePixelCount>, _ flags: vImage_Flags) -> vImage_Error 他にも複数メソッドが用意されていますので覚書しておきます 画像形式 意味 ARGB8888 各チャネル8bitの32bit形式 ARGBFFFF 各チャネル32bitの128bit形式 Planar8 モノクローム画像 8bit PlanarF モノクローム画像 32bit CT画像など,医療用画像は16bitモノクロ画像のことが多いですし, デジカメRAWデータも14か16bitのことが多いでしょう. 試してないですが,おそらく上記で応用可能かなと思います. 実装 基本的には先日紹介したものと同様ですが,新たに  vImageHistogramCalculation_Planar8 を実装していきます. func histogramCalculationGray(imageRef: CGImage) -> [UInt] { let imgProvider: CGDataProvider = imageRef.dataProvider! let imgBitmapData: CFData = imgProvider.data! var imgBuffer = vImage_Buffer( data: UnsafeMutableRawPointer(mutating: CFDataGetBytePtr(imgBitmapData)), height: vImagePixelCount(imageRef.height), width: vImagePixelCount(imageRef.width), rowBytes: imageRef.bytesPerRow) print(imageRef.bytesPerRow) var histogramBinZero = [vImagePixelCount](repeating: 0, count: 256) histogramBinZero.withUnsafeMutableBufferPointer { zeroPtr in let error = vImageHistogramCalculation_Planar8(&imgBuffer, zeroPtr.baseAddress!, vImage_Flags(kvImageNoFlags)) guard error == kvImageNoError else { fatalError("Error calculating histogram: \(error)") } } return histogramBinZero } 当然,Binは1種類ですので戻り値も変更しています. 結果 ・モノクローム画像 ・カラー画像 cgImageからbyteDataへ変換して画素値0〜255までの出現回数が一致しているか確認しましたが問題なさそうです. [3942, 4536, 6772, 9034, 12216, 16272, 20350, 25557, 30674, 36847, 43879, 51445, 60626, 70644, 79406, 89252, 97990, 106718, 114065, 119935, 126283, 130721, 137958, 142371, 145576, 144142, 139682, 133093, 127644, 121993, 117684, 112832, 108841, 104749, 101299, 98299, 96062, 95825, 93493, 91719, 90269, 88754, 88589, 88997, 89898, 90216, 90405, 89903, 90554, 89603, 89594, 89733, 89547, 89444, 88773, 88341, 87894, 87264, 86499, 85897, 84229, 83563, 82034, 81003, 80204, 79277, 78279, 77188, 75882, 75278, 73916, 73093, 71253, 71049, 69666, 67883, 67431, 66362, 65405, 64184, 62829, 62058, 61442, 59497, 58896, 57886, 56992, 56064, 55016, 53887, 53041, 52341, 51795, 51136, 50304, 49501, 49197, 48071, 47541, 47106, 46100, 45821, 45690, 44588, 44138, 43905, 43381, 42800, 42539, 41936, 41312, 41081, 40262, 40005, 39713, 39020, 38961, 38005, 38031, 37683, 37374, 36740, 36475, 36376, 35963, 35530, 35358, 34928, 34876, 34686, 34001, 33594, 33384, 33099, 32879, 32868, 32671, 32394, 32003, 31868, 31655, 31540, 30997, 30705, 30699, 30429, 30409, 29985, 29633, 29318, 29122, 28992, 29145, 28793, 28490, 28281, 27892, 27813, 27500, 27563, 27350, 27492, 27138, 27136, 26519, 26781, 26730, 26510, 26284, 26254, 26110, 25714, 25990, 25580, 25847, 25389, 25456, 25248, 25139, 25082, 24855, 24713, 24641, 24710, 24393, 24254, 24151, 23939, 23650, 23922, 23807, 23534, 23054, 23109, 22921, 22869, 22836, 22504, 22604, 22314, 22226, 22134, 21969, 21409, 21387, 21324, 21053, 21008, 20916, 20615, 20342, 20159, 20033, 19978, 19874, 19697, 19514, 18959, 18945, 18815, 18629, 17992, 17938, 17780, 17549, 17354, 17212, 16816, 16794, 16399, 16259, 15861, 15438, 15151, 15011, 15017, 15124, 14663, 14302, 13454, 12968, 12730, 12566, 12164, 12512, 12796, 12824, 13695, 14835, 17052, 18763, 22570, 31125, 42722, 56396, 129275] これでひとまず,カラーとモノクロに対応できました. 今回使っていない,ARGBFFFFやPlanarFもそのうち使ってみたら記事にします. それではまた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フレームワークを学ぶ

フレームワークについて調べる 調べた内容をメモに残す。 1件以上 1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

デザインパターンを学ぶ

デザインパターンについて調べる 調べた内容をメモに残す。 1件以上 1. MVP
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

現場で経験した技術を個人アプリに実装する

現場で経験した技術を個人アプリに実装する ソースコードをGitHubに上げる コミットを10件以上 1 2 3 4 5 6 7 8 9 10
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

詰まったところと解決方法をまとめて、 自分のqiitaを作る

詰まったところと解決方法をまとめて、 自分の備忘録を作る 備忘録をメモに残す。 10件以上 1 2 3 4 5 6 7 8 9 10
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

技術力があるエンジニアが書いたコードを読む、 書き方を真似る

技術力があるエンジニアが書いたコードを読む、 書き方を真似る 取り入れようと思った書き方をメモに残す。 5件以上 1. 2. 3. 4. 5.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

技術力があるエンジニアに相談する

実装イメージが湧かない場合や、仕様の実現方法が分からない場合に、技術力があるエンジニアに相談をする。 相談内容をメモに残す 10件以上 3件以上 1 2 3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

paizaで問題集を解く

https://paiza.jp/works/mondai paizaで問題集を解く 問題集を解く 問題セットを6セット以上 1 2 3 4 5 6
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

基本を意識してコーディンングをする

書籍を参考にする 調べた内容をメモに残す 3件以上 10件以上 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

swift classとstruct違い

classは継承できるが、structは継承できない classはオプショナル型でないプロパティを値をセットせずに宣言した場合、イニシャライザをクラス内に書く必要がある。structは書く必要がなくデフォルトで補完で宣言してくれる。 classは参照型でstructは値型である。参照型は例えば同じインスタンスを異なる変数名で2つ定義し、プロパティに値をセットするとそれが2つのインスタンス全てに変更が反映されてしまう。 値型の場合は、一つのインスタンスのプロパティに値をセットしてもそれがもう一つの同じインスタンスの方に反映されない。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VaporのLeaf ライブラリ

Swift WebFramework VaporにはLeafという便利なライブラリがあります。 その紹介と使ってみた感想です。 用語 Vapor Leaf:  Vaporライブラリの一つで、HTMLテンプレートエンジンと呼ばれるものです。 HTMLテンプレートエンジン:  Webページを表示するにはHTML言語を使用しますが、直接HTMLを書かずに、メタプログラミング言語を使用して動的に生成する機能の事です。 メタプログラミング言語:  プログラムを生成するためのプログラム言語です。今回は「Leaf」がそれに該当します。 Leaf構文の例 とりあえずLeafの例を見ていきます(ソースコード)。下記では、Swift側で用意したInt型配列を、HTML側で表示しています。Leafではテンプレート識別子に「#」を利用し、それで囲われた領域がテンプレート構文となります。 せっかくなので、プロジェクトを生成するところから始めます。必要なライブラリをインストールするので、PCはインターネットに接続して下さい。 Macのターミナル % cd ~/Desktop Desktop % vapor new --name LeafSample Desktop % cd LeafSample LeafSample % open Package.swift PC がインターネットに接続されていれば、「Package.swift」を開いた時点で自動的に必要なライブラリがダウンロードされます。 Pacage.swiftが開いたら、Vaporが「Leaf」ファイルを見つけられるように、XcodeのメニューからSchemeの変更をします。(Resources/Views配下に「index.leaf」があります) Xcode上部メニューの「Product」-->「Scheme」-->「Edit Scheme...」 左側「Run」-->右側タブ「Options」の「Working Directory」にチェック プロジェクトフォルダを選択(今回は、「/Users/(ユーザー名)/Desktop/LeafSample」) 次にソースコードを書いていきます。(コメントは調整しています) configure.swift import Leaf import Vapor public func configure(_ app: Application) throws { // Leaf を使用 app.views.use(.leaf) try routes(app) } routes.swift import Vapor func routes(_ app: Application) throws { // 「http://localhost」でGETリクエストを受け付けた時のレスポンス設定 // SampleContext構造体を index.leaf に反映させてクライアント側に返しています app.get { req in return req.view.render("index", SampleContext()) } } struct SampleContext: Encodable { let title: String = "Leaf Sample" let a: [Int] = [1, 2, 3, 4] } index.leaf <--! SampleContext構造体を反映させつつHTMLを生成 --> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>#(title)</title> </head> <body> <p>#(title)</p> #for(i in a): <p> #if(i == 1): #(i) #elseif(2 <= i && i < 4): #((i + 1) * i) #else: A #endif </p> #endfor </body> </html> これで準備完了です。 ⌘+Rで実行して、ブラウザからlocalhostにアクセス(Vaporはデフォルトで8080番ポートで待機)すると、 ブラウザ上の表示 Leaf Sample 1 6 12 A 生成されたHTML <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Leaf Sample</title> </head> <body> <p>Leaf Sample</p> <p>1</p> <p>6</p> <p>12</p> <p>A</p> </body> </html> テンプレート構文での改行などの影響で、生成されたHTMLはあまり綺麗ではないかもしれませんが、内容はこれと同一のはずです。 テンプレート構文で使用できる演算 具体例を見て頂いたので、ここからはLeafで使用できる演算を見ていきます。バージョンは、LeafKit:1.1.0 です。 演算の定義は「LeafParameterTypes.swift」ファイルの「LeafOperator」列挙体にあります。 // https://github.com/vapor/leaf-kit.git // Sources/Leafkit/LeafLexer/LeafParameterTypes.swift public enum LeafOperator: String, Equatable, CustomStringConvertible, CaseIterable { // MARK: Public - Cases // Operator types: Logic Exist. UnPre Scope // | Math | Infix | UnPost | // Logical Tests -------------------------------------------- case not = "!" // X X case equal = "==" // X X case unequal = "!=" // X X case greater = ">" // X X case greaterOrEqual = ">=" // X X case lesser = "<" // X X case lesserOrEqual = "<=" // X X case and = "&&" // X X case or = "||" // X X // Mathematical Calcs // ----------------------------------------- case plus = "+" // X X case minus = "-" // X X X X case divide = "/" // X X case multiply = "*" // X X case modulo = "%" // X X // Assignment/Existential // case assignment = "=" // X X case nilCoalesce = "??" // X X case evaluate = "`" // X X // Scoping case scopeRoot = "$" // X X case scopeMember = "." // X X case subOpen = "[" // X X case subClose = "]" // X X // ...(略) この中でさらに、現在使用出来るもの、まだ使用出来ないものに分けられます。 // 上記と同じファイルの少し下 private static let states: [String: Set<LeafOperator>] = [ "logical" : [not, equal, unequal, greater, greaterOrEqual, lesser, lesserOrEqual, and, or], "mathematical" : [plus, minus, divide, multiply, modulo], "existential" : [assignment, nilCoalesce, minus, evaluate], "scoping" : [scopeRoot, scopeMember, subOpen, subClose], "unaryPrefix" : [not, minus, evaluate, scopeRoot], "unaryPostfix" : [subClose], "infix" : [equal, unequal, greater, greaterOrEqual, lesser, lesserOrEqual, and, or, plus, minus, divide, multiply, modulo, assignment, nilCoalesce, scopeMember, subOpen], "unavailable" : [assignment, nilCoalesce, evaluate, scopeRoot, scopeMember, subOpen, subClose] ] ここの「unavailable」keyと対応している演算はまだ使用出来ません。 まだコードが無いので予想ですが、例えば「assignment」は代入、「subOpen, subClose」は配列の要素を、メタプログラミング上で表現するために用意しているのかもしれません。 Leaf Tags 続いては、for, if文のような構文の紹介です。Leafではタグ(Tag)と呼びます。 for, if文の他で、デフォルトで用意されているタグには次のものがあります。  ・lowercased:小文字化  ・uppercased:大文字化  ・capitalized:単語の先頭文字を大文字化  ・contains:配列に指定した要素が含まれているか判定  ・date:1970年1月1日以降の経過時間(秒)を年月日に変換  ・count:配列の要素数 これらは下記で定義されています。 Source/LeafKit/LeafSyntax/LeafTag.swift import Foundation public protocol LeafTag { func render(_ ctx: LeafContext) throws -> LeafData } public var defaultTags: [String: LeafTag] = [ "lowercased": Lowercased(), "uppercased": Uppercased(), "capitalized": Capitalized(), "contains": Contains(), "date": DateTag(), "count": Count() ] struct Lowercased: LeafTag { func render(_ ctx: LeafContext) throws -> LeafData { guard let str = ctx.parameters.first?.string else { throw "unable to lowercase unexpected data" } return .init(.string(str.lowercased())) } } struct Uppercased: LeafTag { func render(_ ctx: LeafContext) throws -> LeafData { guard let str = ctx.parameters.first?.string else { throw "unable to uppercase unexpected data" } return .init(.string(str.uppercased())) } } struct Capitalized: LeafTag { func render(_ ctx: LeafContext) throws -> LeafData { guard let str = ctx.parameters.first?.string else { throw "unable to capitalize unexpected data" } return .init(.string(str.capitalized)) } } struct Contains: LeafTag { func render(_ ctx: LeafContext) throws -> LeafData { try ctx.requireParameterCount(2) guard let collection = ctx.parameters[0].array else { throw "unable to convert first parameter to array" } let result = collection.contains(ctx.parameters[1]) return .init(.bool(result)) } } struct DateTag: LeafTag { func render(_ ctx: LeafContext) throws -> LeafData { let formatter = DateFormatter() switch ctx.parameters.count { case 1: formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" case 2: guard let string = ctx.parameters[1].string else { throw "Unable to convert date format to string" } formatter.dateFormat = string default: throw "invalid parameters provided for date" } guard let dateAsDouble = ctx.parameters.first?.double else { throw "Unable to convert parameter to double for date" } let date = Date(timeIntervalSince1970: dateAsDouble) let dateAsString = formatter.string(from: date) return LeafData.string(dateAsString) } } struct Count: LeafTag { func render(_ ctx: LeafContext) throws -> LeafData { try ctx.requireParameterCount(1) if let array = ctx.parameters[0].array { return LeafData.int(array.count) } else if let dictionary = ctx.parameters[0].dictionary { return LeafData.int(dictionary.count) } else { throw "Unable to convert count parameter to LeafData collection" } } } countタグの使用例: index.leaf <!-- (略)--> <p>#(title)</p> <!-- 追加 --> <p>配列の内容を出力します。個数:#count(a)個</p> #for(i in a): <!-- (略)--> ブラウザ上 Leaf Sample 配列の内容を出力します。個数:4個 1 6 12 A カスタムタグ(Custom Tags) デフォルトタグで足りない場合は自分でタグを作成する事が出来ます。 私の場合は、金額を表す数字をカンマ付きの文字列に変換するタグを作成して利用しています。 例:1000000円 ー> 1,000,000円 routes.swift // index.leafに渡す配列の修正 struct SampleContext: Encodable { let title: String = "Leaf Sample" let a: [Int] = [1, 200, 3000, 400000] } configure.swiftに追加 // 数字を、カンマ付き文字列に変換 struct CommaTag: LeafTag { static let name = "comma" func render(_ ctx: LeafContext) throws -> LeafData { guard ctx.parameters.count == 1, let i = ctx.parameters[0].int else { throw "引数には、一つの数字を指定して下さい。" } return .string(String.localizedStringWithFormat("%d", i)) } } 注意点として、configure関数内でタグを登録するのを忘れないで下さい。 configure.swift //...(略) app.leaf.tags[CommaTag.name] = CommaTag() //...(略) index.leaf <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>#(title)</title> </head> <body> <p>#(title)</p> #for(i in a): <p>#comma(i)</p> #endfor </body> </html> ブラウザ上 Leaf Sample 1 200 3,000 400,000 カンマが付きました? Swift or Leaf の判断 上記の数字にカンマを打つ例についてですが、カスタムタグを使わなくても、Leafに渡す前にSwift側で処理しても同じ事が出来ます。 カンマをSwift側で処理してからLeafに渡す import Vapor func routes(_ app: Application) throws { app.get { req -> EventLoopFuture<View> in return req.view.render("index", StringContext()) } } // カンマ付きの文字列にするため、構造体を少し修正しています struct StringContext: Encodable { let title: String = "Leaf Sample" // ここでカンマ付き文字列にしてしまう let a: [String] = [1, 200, 3000, 400000].map { String.localizedStringWithFormat("%d", $0) } } index.leaf <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>#(title)</title> </head> <body> <p>#(title)</p> #for(i in a): <p>#(i)</p> #endfor </body> </html> ブラウザ上はカスタムタグを使用した時と同じ: Leaf Sample 1 200 3,000 400,000 判断基準ですが、Leafで使用するデータの取得はSwift側、画面表示上の修飾はLeafでやるといいかと思います。つまり、Swift側でカンマ付きの処理をするより、少し手間ですが、Custom Tagを作成して、Leaf側でカンマを付けた方が良いのではないでしょうか。 Swift側で修飾処理までやると、 どこまでSwift側やるのか人によってバラ付きそう 追加で「円」などの単位を付けるときにコンパイルし直す必要がある などの懸念があります。 欲しい機能 このように非常に便利なLeafですが、カスタムタグでは対応出来ない機能もあります。 私が欲しい機能は、 Swiftと同じようにチェーンで繋げられる インデックスを用いて配列の要素を取得する などです。 例えば、こんな構造体を routes.swift struct WebsitesContext: Encodable { let title: String = "Leaf Sample" let a: [String] = ["太郎", "花子"] } ブラウザ上で、テーブルでこんな風に表示したいとします。 ブラウザ上 Leaf Sample 番号 名前 1 太郎 2 花子 現状では、Leaf側で、配列のある要素のインデックスを取得する手段はありません。 ですので、無理やりこんなタグを作って configure.swift public func configure(_ app: Application) throws { // 略 app.leaf.tags[IndexTag.name] = IndexTag() // 略 } // 重複無しの配列のみ struct IndexTag: LeafTag { static let name = "index" func render(_ ctx: LeafContext) throws -> LeafData { guard ctx.parameters.count == 2, let a = ctx.parameters[0].array, let i = a.firstIndex(of: ctx.parameters[1]) else { throw "引数には配列とその要素を一つずつ指定して下さい。" } return .int(i + 1) } } index.leaf <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>#(title)</title> </head> <body> <p>#(title)</p> <table> <thead> <tr> <th>番号</th> <th>名前</th> </tr> </thead> <tbody> #for(name in a): <tr> <td>#index(a, name)</td> <td>#(name)</td> </tr> #endfor </tbody> </table> </body> </html> 先ほどのように表示させています。ただこれは配列の要素に重複があれば上手くいきません。 ブラウザ上 Leaf Sample 番号 名前 1 太郎 2 花子 これが、こんな風にLeafで書けたら嬉しいです。 index.leaf <!-- どちらもエラー --> <table> <thead> <tr> <th>番号</th> <th>名前</th> </tr> </thead> <tbody> <!-- チェーンでつなげる & インデックスの取得 --> #(a.enumerated().forEach): <tr> <td>#($0.offset)</td> <td>#($0.element)</td> </tr> #endforEach </tbody> </table> <table> <thead> <tr> <th>番号</th> <th>名前</th> </tr> </thead> <tbody> #(0..<count(a)): <tr> <td>#($0)</td> <!-- [] を使用して配列の表現 --> <td>#(a[$0])</td> </tr> #end </tbody> </table> 補足:VSCodeを拡張子「leaf」のファイルに対応 「leaf」拡張子のファイルはXcodeのシンタックスハイライトやコード補完機能に対応していません。 一応Xcodeのメニューから「Editor」-->「Syntax Coloring」-->「HTML」で、HTMLファイルとして認識してくれるのですが、Leaf構文は認識していませんし、何より一度Xcodeを閉じると再設定が必要で非常に面倒です。 そこで、私はテキストエディタにVSCodeを使用しているのですが、Extensionsの「vapor-leaf」を導入すると、コーディングがし易くなります。他にもあるかもしれませんし、他のエディタにもあるかもしれませんので探してみて下さい。 終わりに 動的にHTMLを生成してくれる、とっても便利なLeafです。 Swiftのジェネリクスなどの特徴を活かして効率良くテンプレートを書けるようになりたい?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[iOS] RootViewControllerを実装して画面を切り替えてみた

はじめに アプリ開発において、ログイン/ログアウトによって画面を切り替えたい状況に至ったので、UIWindowのrootViewControllerを使ってアプリのどこからでも画面を切り替えられるようにしてみました。 下記GIF画像のようにアプリの画面切り替えを実装します。 環境 [Xcode] Version 12.4 [Swift] Version 5.3.2 [iOS] 14.4 [MacOS] 10.15.7  実装手順 1.UIWindowのrootViewControllerに設定するための、RootViewControllerを作成 どうやらrootViewControllerを直接切り替えることはあまり推奨されていないようです。よってrootViewControllerの子ViewControllerを切り替えて、rootViewControllerにはRootViewControllerを常に設定します。 2.今回はアプリの立ち上げ画面 (SplashViewController)を作成し、遷移先の画面の切り替えを行います 3.ログイン画面とメイン画面はNavigationBarにタイトルが出るだけの単純なものを作っています。よって省略します。 本アプリではSceneDelegateが必要ないため、SceneDelegateを削除してAppDelegateを経由してWindowのrootViewControllerを取得しています。iOS13以降でSceneDelegateを使わずにAppDelegateを使う方法についての説明は本記事では省きます。 私は、この記事が分かりやすかったので参考にさせていただきました。 RootViewController RootViewController import UIKit class RootViewController: UIViewController { //現在のアプリケーションの状態を追跡するために、現在のViewControllerを指す変数を作成 private var current = UIViewController() override func viewDidLoad() { super.viewDidLoad() //viewをロードするとすぐにSplashViewControllerが呼ばれることとする current = SplashViewController() addChild(current) current.view.frame = view.bounds view.addSubview(current.view) current.didMove(toParent: self) } //メイン画面への遷移メソッド func switchToMainScreen() { let mainViewController = MainViewController() let new = UINavigationController(rootViewController: mainViewController) animateFadeTransition(to: new) } //ログイン画面へ遷移するメソッド func switchToLogin() { let loginViewController = LoginViewController() let new = UINavigationController(rootViewController: loginViewController) animateFadeTransition(to: new) } //メイン画面に遷移する際のアニメーションメソッド private func animateFadeTransition(to new: UIViewController, completion: (() -> Void)? = nil) { current.willMove(toParent: nil) addChild(new) //ページ遷移 transition(from: current, to: new, duration: 0.3, options: [.transitionCrossDissolve, .curveEaseOut], animations: {}) { (completed) in self.current.removeFromParent() new.didMove(toParent: self) self.current = new //完了 completion?() } } //上記遷移メソッドの中身の各コードを説明 (showLoginScreen()というメソッドがあると仮定します) func showLoginScreen() { //遷移先のViewControllerオブジェクトを作成 let loginViewController = LoginViewController() let new = UINavigationController(rootViewController: loginViewController) //それをRootViewControllerの子ViewControllerとして追加 addChild(new) //Viewのフレームを位置合わせ new.view.frame = view.bounds //そのビューをaddSubView view.addSubview(new.view) //新しいViewControllerを追加した直後に呼び出す必要あり new.didMove(toParent: self) //willMoveを呼び出して、現在の子ViewControllerを削除する準備 current.willMove(toParent: nil) //現在のビューをスーパービューから削除 current.view.removeFromSuperview() //現在の子ViewControllerを親のRootViewController切り離す current.removeFromParent() //最後に現在の子viewControllerを更新することを忘れずに。 current = new } SplashViewController SplashViewController import UIKit //ユーザーの状況によって、アプリ起動後の遷移先を切り替える class SplashViewController: UIViewController { //処理進行を示すインジケータ private lazy var activityIndicator: UIActivityIndicatorView = { let activityIndicator = UIActivityIndicatorView(style: .large) activityIndicator.frame = view.bounds activityIndicator.color = .white activityIndicator.backgroundColor = UIColor(white: 0.5, alpha: 0.4) return activityIndicator }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white view.addSubview(activityIndicator) //アプリ起動時の画面切り替え makeScreenTransition() } //ユーザーの状態によって画面を切り替えるメソッド private func makeScreenTransition() { //1秒後に止まる遅延処理 activityIndicator.startAnimating() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) { self.activityIndicator.stopAnimating() //今回はログイン状況をデバイスに保存している想定です if UserDefaults.standard.bool(forKey: "isLogin") { //メイン画面へ AppDelegate.shared.rootViewController.switchToMainScreen() } else { //ログイン画面へ AppDelegate.shared.rootViewController.switchToLogin() } } } } AppDelegate AppDelegateのシングルトンを作成して、どこからでも呼び出して画面を切り替えられるようにしています。 AppDelegate import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let window = UIWindow(frame: UIScreen.main.bounds) self.window = window //RootViewControllerを設定 window.rootViewController = RootViewController() window.makeKeyAndVisible() return true } } extension AppDelegate { //シングルトン static var shared: AppDelegate { return UIApplication.shared.delegate as! AppDelegate } var rootViewController: RootViewController { return window!.rootViewController as! RootViewController } } 最後に RootViewControllerが画面の切り替えを担うので、私のようにシンプルな設計のアプリを開発している場合には実装、管理の容易な方法と思いました。 今後、より複雑な状況への対応や、より良い実装方法があれば学んで記事にしていけたらと思います。 参考文献 この記事は以下の情報を参考にしました。 - iOS: Root Controller Navigation - iOS13でSceneDelegateを使わないでアプリを作る
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカルでgitignoreしていたファイルも含めてBitriseでビルドしたい

はじめに 今回は多くの企業で利用されているモバイルアプリCI/CDのBitriseを自分のアプリでも動かしてみようと思いBitriseで環境を構築しました。 その際GitHub上にプッシュしていないAPIキーの入ったplistファイルをBitrise上でどう生成させるかという壁に直面したのでそれを記事にしました。 目次 Bitrise導入 Secretsの設定とファイルの生成 Bitrise導入 今回はiOSアプリで開発をしたので以下の公式手順を元にBitriseにiOSアプリを追加します。 https://devcenter.bitrise.io/jp/getting-started/getting-started-with-ios-apps/ 試しにビルドしましたがこの時点ではまだリモートリポジトリのファイルをBitriseに移しただけなので当然テスト時に下のエラーが発生します。 ❌ error: Build input file cannot be found: '/Users/vagrant/git/[アプリ名]/Resources/Key.plist' (in target '[アプリ名]' from project '[アプリ名]') 今回はAPIキーの内容をKey.plistに保存するようにしています。 ここからビルド時にファイルKey.pistのファイルを生成するようにワークフローを追加していきます。 Secretsの設定とファイルの生成 BitriseのWorkFlowエディタ内のSecretsでシークレット環境変数を設定できるのでそこで.gitignoreしていたファイルの内容をコピペして貼り付けます。ただファイルの中身は数十行になっておりそれらを1行にして管理したかったのでbase64でエンコードした状態で値を設定しました。 base64については下の記事を参考にしてください。 https://qiita.com/PlanetMeron/items/2905e2d0aa7fe46a36d4 Expose for Pull Requests? の設定はデフォルトでは❎ になっていますがこれだとGitHubでプルリクエストを出してBitriseを回すときにこのSecretsが利用できずエラーになってしまうため✅ にします。 次にWorkflowでSecretsを利用してファイルの生成をします。 Workflowsの項目で + ボタンからStepの追加をします。今回はファイルを生成するためにシェルスクリプトを利用するのでScriptという名前のステップを追加します。(画像でいうと上から3つめ) アプリをクローンしてからファイルを生成しなければ実行は失敗するので必ずGit Clone Repositoryの後にステップを追加するようにしてください。 その後Scriptのステップを押すとScriptの内容を入力するテキストがあるのでそこで echo ${NEWS_API_KEY_PLIST_BASE} | base64 --decode > [アプリ名]/[ignoreしたファイルパス] を記述することによってテスト時にignoreしたファイルが生成され${NES_API_KEY_PLIST_BASE}の中身が書き込まれます。今回はbase64で値をエンコーディングしていたため書き込み時にデコードさせていますが特に必要がない場合は|base64 --decodeの部分を消去して実行してください。 この状態でもう一度Bitriseを実行します。 今度はビルド成功させることができました。 参考文献 Bitrise base64ってなんぞ??理解のために実装してみた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コードで画面遷移 swift

■何故コードで画面遷移するのか? ■画面が多い場合 Main.Storyboard上で画面遷移すると蜘蛛の巣上に遷移同士の線が重なってしまうため 見にくい ■ログイン画面があるアプリ ログインした事がある場合とない場合で始まりの画面が違う ない場合はログイン画面でIDやパスワードを打つ画面を表示する必要がある。 ある場合ログイン画面を飛ばして最初の画面を表示しなければならない。 こういった条件をコードで書く。 Main.Storyboardは基本使わない。 ■Main.Storyboardを消す。 まず初めに そのまま消すとエラーになる。 この記事を通りにするとエラー消えます! めちゃわかりやすく載ってます! https://qiita.com/sakiyamaK/items/cf95112a28e84b4c6dff ■1 準備 今時は1つのStoryboardに1つのViewControllerが基本なので FistViewControllerとFist.Storyboard用意 紐づける SecondeViewControllerとSeconde.Storyboard用意 紐づける 互いのStoryboardにする事 ■UIView用意 ■is initial View Controllerにチェック First.StoryboardにButtonを設置 2 新たにRouter.swiftを用意 初めの画面を用意するshowRootメソッドを準備する。 import UIKit class Router { static func showRouter(window:UIWindow?) { let vc = UIStoryboard.init(name: "First", bundle: nil).instantiateInitialViewController() as! FirstViewController let nav = UINavigationController(rootViewController: vc) window?.rootViewController = nav window?.makeKeyAndVisible() } } 3 AppDelegate.swiftにて先ほどのshowRouterメソッドを呼び出す。 import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let window = UIWindow(frame: UIScreen.main.bounds) self.window = window Router.showRoot(window: window) return true } } ここまでで最初の画面は表示できている。 次にbuttonを押して画面遷移をしていくコードを書く 4 Router.swiftに下記コードを足していく! static func showSeconde(from: UIViewController) { let vc = UIStoryboard.init(name: "Seconde", bundle: nil).instantiateInitialViewController() as! SecondeViewController show(from: from, next: vc) } //実際に画面遷移するコード //NavigationControllerならば横からスライド方式で遷移 //そうでなければModalで遷移下から画面が出てくる。 static func show(from: UIViewController, next: UIViewController) { if let nav = from.navigationController { nav.pushViewController(next, animated: true) } else { from.present(next, animated: true, completion: nil) } } 5 FistViewController import UIKit class FirstViewController: UIViewController { //ボタンを紐づけてボタンを押したらshowSwcondeメソッドを呼ぶ @IBAction func nextButton(_ sender: Any) { Router.showSeconde(from: self) } } これでコードで画面遷移が行えます。 以上です。 これを基本にアプリ作成を行なっていった方が良さそうです!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】iOSアプリでFirestoreからデータを取得する方法

昔、AWSを使って個人アプリを開発していたのですが、APIを作ってEC2に配置して、RDS設定して・・・ とにかく色々大変だったので、もっと簡単にCRUD機能を実現したいなと思いました 今更かもしれませんが、FirestoreでAPIを作らずに簡単にCRUD機能を実現できることを知ったので、今回はデータを取得するところまでをやってみたいと思います ソースコードはこちらから↓ 環境 Xcode12.4 Swift5 Firebaseと連携する 下記の公式ドキュメントに沿って、iOSアプリとFirebsseの連携をしてみる 新規プロジェクト作成 Firebaseコンソール画面から新規にプロジェクトを追加する プロジェクト追加 任意のプロジェクト名を記載して続行 今回はGoogleアナリティクスへの連携はOFFにして、プロジェクトを作成 ONにすると、次の画面で連携するGoogleアカウントを指定する必要があります 新規にプロジェクトが作成されました Firestoreでデータベース作成 早速、FireStoreでデータを作成していきます コンソール画面からFirestore Databaseを選択してデータベースを作成 テストモードを選択して次に行きます 公式ドキュメントによると、本番環境モード(ドキュメントではロックモード)では、モバイルアプリから直接Firestoreのデータを参照できないため、APIやBatchを経由する必要があるみたいです。 そのため、今回はモバイルアプリから直接データを参照できるテストモードを選択します ロックモード モバイルおよびウェブ クライアントからのすべての読み書きを拒否します。認証されたアプリケーション サーバー(C#、Go、Java、Node.js、PHP、Python、Ruby)は引き続きデータベースにアクセスできます。 https://firebase.google.com/docs/firestore/quickstart?hl=ja#create ロケーションをは利用場所から一番近いasia-northeast1(東京)を選択します 日本にはロケーションが東京と大阪にあるみたいです ロケーションは一度しか設定できないため要注意です asia-northeast1 東京 asia-northeast2 大阪 https://firebase.google.com/docs/firestore/locations?hl=ja#types データ追加 今回はコンソール画面からPersonデータを2件追加しました Firestoreだと配列の中に配列を入れることができないため、今回はhobbies配列の中にmapタイプでnameやyearのデータを入れました。referenceタイプを指定すると、別のドキュメントを指定することも可能です。 ちなみに、コレクションやドキュメントの説明については、以下の記事がとてもわかりやすかったです ライブラリをインストール Firestoreのライブラリを入れていきます。今回はCocoaPodsを利用します。 Podsの初期化については以下の記事が参考になると思います。 指定のライブラリをPodfileに追加します ※Swiftを利用する場合は、FirebaseFirestoreSwiftも追加するらしい。 pod 'Firebase/Firestore' # Optionally, include the Swift extensions if you're using Swift. pod 'FirebaseFirestoreSwift' インストールします pod install Firebseの初期化 Firestoreを利用する際に初期化が必要となるため、Appdelegateで初期化しておきます import UIKit import Firebase @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Firebase初期化 FirebaseApp.configure() return true } } ビルドしたらコンパイルエラーになりました・・・ Terminating app due to uncaught exception 'com.firebase.core', reason: '`[FIRApp configure];` (`FirebaseApp.configure()` in Swift) could not find a valid GoogleService-Info.plist in your project. Please download one from https://console.firebase.google.com/.' そもそもFirebaseにiOSプロジェクトを追加していなかったです 初期設定の際にGoogleService-Info.plistを追加するのですが、今回はそのファイルがなくてエラーとなっています FirebaseにiOSプロジェクト追加 少し話が前後してしまい申し訳ないですが、FirebaseにiOSプロジェクトを追加します コンソール画面でiOSを選択 BundleIDとニックネームを記載 GoogleService-Info.plistをダウンロード 後続は前述したようにpodでインストールとFirebaseの初期化処理なので、割愛します GoogleService-Info.plistをプロジェクトのRoot配下に追加 無事にビルドできました(失礼しました!) Firestoreからデータ取得 Firestoreで追加したデータを構造体(Person,Hobby)に格納しました データは非同期で取得されます ViewController.swift import UIKit import Firebase struct Person { var name: String var age: Int var hobbys: [Hobby] } struct Hobby { var name: String var year: Int } class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // リスト初期化 var persons: [Person] = [] // FirestoreのDB取得 let db = Firestore.firestore() // personsコレクションを取得 db.collection("persons").getDocuments() { collection, err in // エラー発生時 if let err = err { print("Error getting documents: \(err)") } else { // コレクション内のドキュメントを取得 for document in collection!.documents { // hobbiesフィールドを取得 guard let hobbyDicList: [[String : Any]] = document.get("hobbies") as? [[String : Any]] else { continue } // リスト初期化 var hobbies: [Hobby] = [] // hobbies内のフィールドを取得 for hobbyDic in hobbyDicList { guard let hobbyName = hobbyDic["name"] as? String , let hobbyYear = hobbyDic["year"] as? Int else { continue } // Hobbyを作成 let hobby = Hobby(name: hobbyName, year: hobbyYear) // リストに追加 hobbies.append(hobby) } // Personフィールドを取得 guard let personName = document.get("name") as? String, let personAge = document.get("age") as? Int else { continue } // Personを作成 let person = Person(name: personName, age: personAge, hobbys: hobbies) // リストに追加 persons.append(person) } } // コンソール出力 print(persons) } } } 出力結果より、Firestoreに追加したデータを無事に取得することができました! [firestore_sample_app.Person(name: "jiro", age: 35, hobbys: [firestore_sample_app.Hobby(name: "climbing", year: 10)]), firestore_sample_app.Person(name: "taro", age: 20, hobbys: [firestore_sample_app.Hobby(name: "baseball", year: 5), firestore_sample_app.Hobby(name: "tennis", year: 8)])] おわりに 今回、Firestoreを利用してみましたが、とても使いやすかったです シンプルな機能ならFirebaseだけで事足りそうだなと感じました ユーザー認証、ストレージ機能など他にも様々な機能が用意されているので、色々試してみたいと思います (サーバ側はFirebaseに任せてiOSアプリに注力できそうです) 最後まで読んで頂きありがとうございます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftをPlaygroundsで書こう

Playgroundsとは PlaygroundsとはAppleが開発したSwiftの書き方を学べるmac/ipadアプリです。最近はmicro bitやLEGO、ドローンといったものとの連携も見られ始めています。教育的側面が強く子供向けというイメージがあるかもしれませんが、「ちょっとSwiftで試したいことがあるけどわざわざXCodeで確認するのもなー」といった時に重宝します。 UIViewControllerを書く 準備 「書く」という表現が適切かはわかりませんが。とりあえず、Xcodeを立ち上げて最初に開かれる状態を再現します。import PlaygroundSupportを最初にPlaygroundPage.current.liveView = ViewControlelr()を最後に加えなければいけません。 import UIKit import PlaygroundSupport class ViewControlelr: UIViewController { override func viewDidLoad() { super.viewDidLoad() print("viewDidLoad") } } PlaygroundPage.current.liveView = ViewControlelr() 実行すると、それらしきものができていることがわかります。 背景色を変更する これはPlaygroundsならではというわけではありませんが。 import UIKit import PlaygroundSupport class ViewControlelr: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .gray print("viewDidLoad") } } PlaygroundPage.current.liveView = ViewControlelr() 背景色を変更し、先程の黒い画面がViewを表していることが確認されました。 右下の赤く光っているボタンを押すと、メッセージが表示されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftPM で C言語を含むライブラリを作る

SwiftPM で C言語を含むライブラリを作った C++ で作成された音声分析合成システムWORLD の Swift ラッパーである WorldInApple を作成しています Swift Package Manager に対応した話です 動作の様子 worldのswiftのwrapperを公開しましたーiPhoneでボイチェンができます!https://t.co/t2Y3BvhvLi pic.twitter.com/b7AI4F1Qkr— ふじき (@fzkqi) February 17, 2020 Vocoder 自体の話はこちら WorldInApple の実装 やったこと 公式ドキュメントの Creating C Language Targets の通りです C++ で実装された World 本体のターゲットを作成し、Swift のラッパーのターゲットから依存させました Package.swift -> https://github.com/fuziki/WorldInApple/blob/develop/Package.swift import PackageDescription let package = Package( name: "WorldInApple", platforms: [.iOS(.v11), .macOS(SupportedPlatform.MacOSVersion.v10_15)], products: [ .library( name: "WorldInApple", targets: ["WorldInApple"]), ], targets: [ // C++ で書かれた、World 本体 .target( name: "WorldLib" ), // WorldLib の Swift ラッパー .target( name: "WorldInApple", dependencies: ["WorldLib"]), // WorldLib に依存 ] ) WorldLib C++ で実装された World 本体です SwiftPM で USER_HEADER_SEARCH_PATHS を指定する方法を見つけられなかったので、コピーしてヘッダを編集しています 差分は、パッチファイルにまとめています https://github.com/fuziki/WorldInApple/blob/develop/fix-world-header-path.patch WorldLib/include/World.h -> https://github.com/fuziki/WorldInApple/blob/develop/Sources/WorldLib/include/World.h WorldLib の公開されるヘッダです World の必要なヘッダを公開しています #import "../world/dio.h" #import "../world/stonemask.h" #import "../world/cheaptrick.h" #import "../world/d4c.h" #import "../world/synthesis.h" #import "../world/harvest.h" #import "../world/synthesisrealtime.h" #import "../world/matlabfunctions.h" WorldInApple WorldLib の Swift ラッパーです import WorldLib することで、World て定義された機能を利用できます -> https://github.com/fuziki/WorldInApple/blob/develop/Sources/WorldInApple/WorldInAppleComponents/AperiodicityEstimator.swift import WorldLib public class AperiodicityEstimator: WorldInAppleComponents { private var d4cOption = D4COption() // World で定義された構造体が利用できる //〜中略〜 public func estimatAperiodicity(x: UnsafeMutablePointer<Double>, x_length: Int32) { // World で定義された関数が利用できる D4C(x, x_length, Int32(parameters.fs), parameters.time_axis, parameters.f0, Int32(parameters.f0_length), Int32(parameters.fft_size), &d4cOption, parameters.aperiodicity) } } サンプルの実装 (Examples) WorldInApple の パッケージから参照されないようにダミーの Package.swift を配置しています -> https://github.com/fuziki/WorldInApple/blob/develop/Examples/Package.swift import PackageDescription let package = Package( name: "dummy", products: [], targets: [] ) WorldInApple.xcworkspace から実行可能です Examples プロジェクトと、WorldInApple のパッケージが入っています Examples ターゲットから WorldInApple を参照しています import WorldInApple することで、作成した World の Swift のラッパーを利用可能です -> https://github.com/fuziki/WorldInApple/blob/develop/Examples/Examples/Shared/ContentViewModel.swift import AVFoundation import Combine import Foundation import SwiftUI import WorldInApple class ContentViewModel: ObservableObject { private let world = WorldInApple(fs: 48000, frame_period: 5, x_length: 38400) // 〜中略〜 } 公開する & 利用する GitHub に push することで、pure swift のパッケージ同様に利用することが可能でした
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【algorithm】シェルソート、クイックソート

はじめに 今回はシェルソートとクイックソートを紹介したいと思います! シェルソート 間隔を空けて挿入ソートを行い、その間隔をだんだん狭めていく方法 特徴は ・少ないデータであればソートは高速になる ・大雑把でも順番に並んでいる部分が多いと挿入ソートは高速にソートできる 考え方として、 ・グループ分けの間隔を半分にしていく繰り返しを行う ・その中で各グループに対して挿入ソートを行う var nums = [5, 3, 14, 15, 7, 9, 13, 1, 2, 4, 12, 11, 8, 6, 10] var step = Int(nums.count / 2) while step > 0 { for i in step..<nums.count { let t = nums[i] var j = i while j >= step { if nums[j-step] > t { nums[j] = nums[j-step] j -= step } else { break } } nums[j] = t } step = Int(step / 2) } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] クイックソート 少し難しいです。 配列の中の小さいデータを左に集め、大きいデータを右に集め、大小二つのグループに分けます。それぞれのグループで同様に左に小さい、右に大きいデータを集め、二つのグループに分ける。これを繰り返していくと、最終的にソートされた配列が完成します。 ・真ん中にある値を基準にしてデータを大小二つのグループに分割する ・分割したグループのそれぞれに同じ処理を繰り返す。 ・交換回数を減らすために交換する必要があるものだけで交換を行う ・アルゴリズムをシンプルにするために、再帰を使う ちなみに、基準となる値をピボットと言います。 「再帰」とは、自分で自分自身を呼び出すアルゴリズムです。 今回は分割した部分に対してさらに分割して同じような処理をしていくという階層的な繰り返しになっているので、このような回想的な繰り返しには再帰を使います。 var nums = [5, 3, 14, 15, 7, 9, 13, 1, 2, 4, 12, 11, 8, 6, 10] func quickSort(startID: Int, endID: Int) { let pivot = nums[Int((startID + endID) / 2)] var left = startID var right = endID while true { while nums[left] < pivot { left += 1 } while pivot < nums[right] { right -= 1 } if right <= left { break } let t = nums[left] nums[left] = nums[right] nums[right] = t left += 1 right -= 1 } if startID < left - 1 { quickSort(startID: startID, endID: left - 1) } if right + 1 < endID { quickSort(startID: right + 1, endID: endID) } } quickSort(startID: 0, endID: nums.count - 1) print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] おわりに 次回から応用的なアルゴリズムをみていきます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【algorithm】単純交換法、単純選択法、単純挿入法

はじめに 今回は整列(ソート)アルゴリズムについてまとめていこうと思います! 単純交換法(バブルソート) 全ての隣り合った値を比べていき、小さい方が前に移動するように交換する方法 総当たりてきなアルゴリズムなので、大量データには向かない var nums = [5, 3, 7, 9, 1, 2, 4, 8, 6] for i in 0..<nums.count { for j in stride(from: nums.count-1, to: i, by: -1) { if nums[j] < nums[j-1] { let t = nums[j] nums[j] = nums[j-1] nums[j-1] = t } } } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9] まず、前からソート済みを省いた要素にアクセスするためfor文と後ろから二つを比較して行くためのfor文の二つのfor文が必要です。 stride(from: nums.count-1, to: i, by: -1)で配列を逆順でアクセスできます。 あとは、二つを比較していき、左の方が大きい場合は交換のアルゴリズムを使って交換します。交換のアルゴリズム 単純選択法(選択ソート) 最小値を探して先頭から順番に並べていく方法 最小値をを入れる位置を「選択」し、最小値と交換していくので、選択ソートという 最小値と交換のアルゴリズムを使います。最小値のアルゴリズム, 交換のアルトリズム var nums = [5, 3, 7, 9, 1, 2, 4, 8, 6] for i in 0..<nums.count { var min = nums[i] var k = i for j in (i+1)..<nums.count { if min > nums[j] { min = nums[j] k = j } } let t = nums[i] nums[i] = nums[k] nums[k] = t } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9] ポイントは、最小値の位置を表す変数も用意してその位置を保存しておくことです。 単純挿入法(挿入ソート) データを抜き出して正しい位置に挿入していく方法 ・整列していない部分から挿入する値を順番に一つずつ取り出す繰り返しをする ・その中で取り出した整列した部分のどこに挿入すればよいかをみていく繰り返しをする var nums = [5, 3, 14, 15, 7, 9, 13, 1, 2, 4, 12, 11, 8, 6, 10] for i in 1..<nums.count { let t = nums[i] var insert = 0 for j in stride(from: i-1, through: 0, by: -1) { if nums[j] > t { nums[j+1] = nums[j] } else { insert = j + 1 break } } nums[insert] = t } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] おわりに 今回は単純なソート方法を紹介しました!次回は少し難しいシェルソートとクイックソートについてまとめていこうと思います!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【algorithm】シェルソート、クイックソート

はじめに 今回はシェルソートとクイックソートを紹介したいと思います! シェルソート 間隔を空けて挿入ソートを行い、その間隔をだんだん狭めていく方法 特徴は ・少ないデータであればソートは高速になる ・大雑把でも順番に並んでいる部分が多いと挿入ソートは高速にソートできる 考え方として、 ・グループ分けの間隔を半分にしていく繰り返しを行う ・その中で各グループに対して挿入ソートを行う var nums = [5, 3, 14, 15, 7, 9, 13, 1, 2, 4, 12, 11, 8, 6, 10] var step = Int(nums.count / 2) while step > 0 { for i in step..<nums.count { let t = nums[i] var j = i while j >= step { if nums[j-step] > t { nums[j] = nums[j-step] j -= step } else { break } } nums[j] = t } step = Int(step / 2) } print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] クイックソート 少し難しいです。 配列の中の小さいデータを左に集め、大きいデータを右に集め、大小二つのグループに分けます。それぞれのグループで同様に左に小さい、右に大きいデータを集め、二つのグループに分ける。これを繰り返していくと、最終的にソートされた配列が完成します。 ・真ん中にある値を基準にしてデータを大小二つのグループに分割する ・分割したグループのそれぞれに同じ処理を繰り返す。 ・交換回数を減らすために交換する必要があるものだけで交換を行う ・アルゴリズムをシンプルにするために、再帰を使う ちなみに、基準となる値をピボットと言います。 「再帰」とは、自分で自分自身を呼び出すアルゴリズムです。 今回は分割した部分に対してさらに分割して同じような処理をしていくという階層的な繰り返しになっているので、このような回想的な繰り返しには再帰を使います。 var nums = [5, 3, 14, 15, 7, 9, 13, 1, 2, 4, 12, 11, 8, 6, 10] func quickSort(startID: Int, endID: Int) { let pivot = nums[Int((startID + endID) / 2)] var left = startID var right = endID while true { while nums[left] < pivot { left += 1 } while pivot < nums[right] { right -= 1 } if right <= left { break } let t = nums[left] nums[left] = nums[right] nums[right] = t left += 1 right -= 1 } if startID < left - 1 { quickSort(startID: startID, endID: left - 1) } if right + 1 < endID { quickSort(startID: right + 1, endID: endID) } } quickSort(startID: 0, endID: nums.count - 1) print("ソート後:", nums) // ソート後: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] おわりに 次回から応用的なアルゴリズムをみていきます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像のヒストグラムを作る(1) (カラー画像編)

画像のヒストグラムを作る Swift5での画像のヒストグラムを作成する良いサンプルが無かったので覚書 公式サンプルに参考になるプロジェクトあり Accelerate.vImageのvImageHistogramCalculation_ARGB8888を利用して計算するよう https://developer.apple.com/documentation/accelerate/1545743-vimagehistogramcalculation_argb8 この方法では,カラーチャネルが1chでは計算できないが,ひとまず目的は達成したので良しとする import Accelerate.vImage func histogramCalculation(imageRef: CGImage) -> (red: [UInt], green: [UInt], blue: [UInt], alpha:[UInt]) { let imgProvider: CGDataProvider = imageRef.dataProvider! let imgBitmapData: CFData = imgProvider.data! var imgBuffer = vImage_Buffer( data: UnsafeMutableRawPointer(mutating: CFDataGetBytePtr(imgBitmapData)), height: vImagePixelCount(imageRef.height), width: vImagePixelCount(imageRef.width), rowBytes: imageRef.bytesPerRow) // bins: zero = red, green = one, blue = two, alpha = three var histogramBinZero = [vImagePixelCount](repeating: 0, count: 256) var histogramBinOne = [vImagePixelCount](repeating: 0, count: 256) var histogramBinTwo = [vImagePixelCount](repeating: 0, count: 256) var histogramBinThree = [vImagePixelCount](repeating: 0, count: 256) histogramBinZero.withUnsafeMutableBufferPointer { zeroPtr in histogramBinOne.withUnsafeMutableBufferPointer { onePtr in histogramBinTwo.withUnsafeMutableBufferPointer { twoPtr in histogramBinThree.withUnsafeMutableBufferPointer { threePtr in var histogramBins = [zeroPtr.baseAddress, onePtr.baseAddress, twoPtr.baseAddress, threePtr.baseAddress] histogramBins.withUnsafeMutableBufferPointer { histogramBinsPtr in let error = vImageHistogramCalculation_ARGB8888(&imgBuffer, histogramBinsPtr.baseAddress!, vImage_Flags(kvImageNoFlags)) guard error == kvImageNoError else { fatalError("Error calculating histogram: \(error)") } } } } } } return (histogramBinZero, histogramBinOne, histogramBinTwo, histogramBinThree) } iOSであれば,UIImageからcgImage作成は用意だが,macではNSImageなのでひと作業必要 var imageRect = NSRect(x: 0, y: 0, width: img.size.width, height: img.size.height) guard let cgImg1 = img.cgImage(forProposedRect: &imageRect, context: nil, hints: nil) else { abort() } 結果 4000x3000の画像で試すと *func: load img, Time: 29.488ms *func: make cgImg 1, Time: 0.069ms *func: calc, Time: 162.501ms *func: make 3ch chart, Time: 0.109ms *func: draw, Time: 0.9739ms 十分な速度が出た グラフはnsbezierPath curveを使うとなめらかになりそうだが control point算出がめんどくさく,今回は直線でつないでいる 上記プロジェクトはGithubにおいておきます ではまた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像のヒストグラムを作る

画像のヒストグラムを作る Swift5での画像のヒストグラムを作成する良いサンプルが無かったので覚書 公式サンプルに参考になるプロジェクトあり Accelerate.vImageのvImageHistogramCalculation_ARGB8888を利用して計算するよう https://developer.apple.com/documentation/accelerate/1545743-vimagehistogramcalculation_argb8 この方法では,カラーチャネルが1chでは計算できないが,ひとまず目的は達成したので良しとする import Accelerate.vImage func histogramCalculation(imageRef: CGImage) -> (red: [UInt], green: [UInt], blue: [UInt], alpha:[UInt]) { let imgProvider: CGDataProvider = imageRef.dataProvider! let imgBitmapData: CFData = imgProvider.data! var imgBuffer = vImage_Buffer( data: UnsafeMutableRawPointer(mutating: CFDataGetBytePtr(imgBitmapData)), height: vImagePixelCount(imageRef.height), width: vImagePixelCount(imageRef.width), rowBytes: imageRef.bytesPerRow) // bins: zero = red, green = one, blue = two, alpha = three var histogramBinZero = [vImagePixelCount](repeating: 0, count: 256) var histogramBinOne = [vImagePixelCount](repeating: 0, count: 256) var histogramBinTwo = [vImagePixelCount](repeating: 0, count: 256) var histogramBinThree = [vImagePixelCount](repeating: 0, count: 256) histogramBinZero.withUnsafeMutableBufferPointer { zeroPtr in histogramBinOne.withUnsafeMutableBufferPointer { onePtr in histogramBinTwo.withUnsafeMutableBufferPointer { twoPtr in histogramBinThree.withUnsafeMutableBufferPointer { threePtr in var histogramBins = [zeroPtr.baseAddress, onePtr.baseAddress, twoPtr.baseAddress, threePtr.baseAddress] histogramBins.withUnsafeMutableBufferPointer { histogramBinsPtr in let error = vImageHistogramCalculation_ARGB8888(&imgBuffer, histogramBinsPtr.baseAddress!, vImage_Flags(kvImageNoFlags)) guard error == kvImageNoError else { fatalError("Error calculating histogram: \(error)") } } } } } } return (histogramBinZero, histogramBinOne, histogramBinTwo, histogramBinThree) } iOSであれば,UIImageからcgImage作成は用意だが,macではNSImageなのでひと作業必要 var imageRect = NSRect(x: 0, y: 0, width: img.size.width, height: img.size.height) guard let cgImg1 = img.cgImage(forProposedRect: &imageRect, context: nil, hints: nil) else { abort() } 結果 4000x3000の画像で試すと *func: load img, Time: 29.488ms *func: make cgImg 1, Time: 0.069ms *func: calc, Time: 162.501ms *func: make 3ch chart, Time: 0.109ms *func: draw, Time: 0.9739ms 十分な速度が出た グラフはnsbezierPath curveを使うとなめらかになりそうだが control point算出がめんどくさく,今回は直線でつないでいる 上記プロジェクトはGithubにおいておきます ではまた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift5.4リリース

この記事はなに Swift.orgのブログに投稿されたSwift5.4のリリースについて、個人的に興味深い部分を読んでみました。 アップデート情報 Swift5.4に含まれる新しい言語機能 関数、添え字、イニシャライザが複数の可変パラメータをサポート(SE-0284) 暗黙的なメンバー構文の拡張(SE-0287) リザルトビルダー(SE-0289) ローカル関数が、オーバーロードをサポート ローカル変数のプロパティラッパー 新しい並列実行モデルでは、識別子としてawaitを修飾せずに使用した場合に、コンパイラは警告と修正を行うようになりました。これらの識別子は、SE-0296の一部としてSwiftの将来のバージョンにおいて、awaitキーワードとして解釈されます。 ランタイム性能とコードサイズの改善 Swift5.4では、以前の搜索結果をキャッシュするハッシュテーブルの実装が高速化されたおかげで、実行時のプロトコル適合性チェックが大幅に高速化されました。特に、一般的な実行時のas?とas!のキャスト操作が高速化されています。 さらに、連続した配列の変更は、冗長な一意性チェックを回避します。 func foo(_ a: inout [Int]) { a[0] = 1 // 必須のcopy-on-writeチェックが発生するところ a[1] = 2 // ここで、余計なCoWチェックは行わない } 他にも、さまざまなパフォーマンスが改善されました。 文字列の補間が、より積極的に定数化 特に関数のinout引数やwhileループにおいて、retain/releaseの呼び出しを削減 標準ライブラリのジェネリックなメタデータがコンパイル時に特殊化され、ダーティメモリの使用量が減り、パフォーマンスを向上 コードの補間 大きな関数のボディにおいて、コード補完のパフォーマンスはより速くなりました。 swift-package-managerリポジトリ内の一例では、そのファイルで繰り返される呼び出しは、Swift5.4のself.のコード補完時間が、Swift5.3と比較して20mから5ms、4倍も速くなっています。 コード補完は、「エラーを含む式」や「文脈が不足している曖昧な式」でも、より信頼できるようになりました。 例を示します。 func test(a: Int, b: String) -> Int { ... } func test(a: Int, b: Int) -> String { ... } func test(a: (Int, Int) -> Int) -> Int { ... } 上記のコードに対するコード補完は、以下のように動作するようになりました。 test( ... ).prefix(3)の後にコード補完を呼び出すと、Stringのメンバーを示唆します。 test(a: 2)の後にコード補完を実行すると、IntおよびString`のメンバーを示唆します。 次のブロックで$0.の後にコード補完を実行すると、Int:test { $0. }のメンバーを示唆します。 型チェッカー Swift5.4では、a + b + (2 * c)のような「連結された式」に対する型チェックのパフォーマンスが向上しています。 次のような例を考えてみます。 struct S { var s: String? } func test(_ a: [S]) { _ = a.reduce("") { ($0 + "," + ($1.s ?? "")) + ($1.s ?? "") + ($1.s ?? "") } } これまで、このコードの型チェックはタイムアウトになっていましたが、100ミリ秒以内に完了するようになりました。 さらに、他のリテラル式を含む配列リテラルをネストした場合のパフォーマンスも向上しました。 例えば、以下のような無効なコードに対して、以前はコンパイラが「複雑すぎて時間内に解決できない」というメッセージを表示していました。 enum E { case first case second case third } let dictionary = [ .first: [0, 1, 2, 3, 4, 5, 6, 7], .second: [8, 9, 10, 11, 12, 13, 14, 15], .third: [16, 17, 18, 19, 20, 21, 22, 23], ] Swift5.4では、このコードが無効であると診断され、正確なエラーメッセージを表示するようになりました。 error: reference to member 'first' cannot be resolved without a contextual type .first : [ 0, 1, 2, 3, 4, 5, 6, 7], ^ error: reference to member 'second' cannot be resolved without a contextual type .second : [ 8, 9, 10, 11, 12, 13, 14, 15], ^ error: reference to member 'third' cannot be resolved without a contextual type .third : [16, 17, 18, 19, 20, 21, 22, 23], ^ 型チェッカーは、無効なステートメント(無効なreturnステートメントなど)、無効な宣言の参照、パターンマッチのエラーなど、リザルトビルダーの診断機能が向上しました。 例えば、以下のようなものです。 import SwiftUI struct ContentView: View { @State private var condition = false var body: some View { Group { if condition { Text("Hello, World!") .frame(width: 300) } else { return Text("Hello, World!") } } } } このコードの場合、型チェッカーは次のようなエラーと同時に、リザルトビルダーを適用するためにreturnを削除する修正案も報告します。 error: cannot use explicit 'return' statement in the body of result builder 'SceneBuilder' return Text("Hello, World!") ^
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift5] User Defined Runtime Attributesを使用して、Storyboardからプロパティの初期値を設定してみた

はじめに UIを設定するプロパティの初期値をStoryboardから設定してみたので、備忘録としてその手順を記します。 環境 [Xcode] Version 12.4 [Swift] Version 5.3.2 実装手順 Storyboardの[Identity Inspector]タブのUser Defined Runtime Attributesに「+」を押して設定するだけです。 「Key Path」にプロパティ名を、「Type」には今回はColorを、「Value」には設定したい色を入れます。 但し、コードでプロパティを定義するときに、@objc dynamicをつける必要があります。 Objective-CのクラスやメソッドをSwiftでも使えるようにするためのキーワードのようです。ここら辺はあまり詳しく調べられていないので説明を省きます。 FirstViewController import UIKit class FirstViewController: UIViewController { @IBOutlet weak var centerLabel: UILabel! //ラベルの背景色を設定する変数に対して、StoryboardからColorを設定する @objc dynamic var labelBackgroundColor = UIColor(red: 0.1, green: 0.2, blue: 0.6, alpha: 1.0) override func viewDidLoad() { super.viewDidLoad() //ラベルの背景色を設定したColorにする centerLabel.backgroundColor = labelBackgroundColor } } 今のところ、Colorを含めて11種類 (Boolean, Number, String, Localized String, Point, Rect, Range, Color, Image, Nil)のTypeを指定できるので、色々試してみたいと思います。 参考文献 この記事は以下の情報を参考にしました。 - dynamic var を理解するための極意 - swift - Swift4でUser Defined Runtime Attributesがうまく効かないところがあった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む