20210510のiOSに関する記事は8件です。

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で続きを読む

画像のヒストグラムを作る(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で続きを読む

SwiftUIに内包されているStructまとめ - ViewLayout編

はじめに SwiftUIフレームワークに内包されている構造体の内、頻繁に利用する構造体を備忘録的にまとめようと思います。 Views Controls Layout ☆本記事ではここが対象 Paints Other Modifiers Controls Effects Layout Text Image List Styles Accessibility Events Gestures Shape Modifiers Other など 今後残りの部分についてもまとめていければと思っています。 また、端末で動作確認できるようにもしているので、もし興味があればローカルで端末にビルドいただければと思います。 ソースコード Views Layout Depth Stack Group { Text("ZStack") ZStack { ForEach(0..<colors.count) { Rectangle() .fill(colors[$0]) .frame(width: 100, height: 100) .offset(x: CGFloat($0) * 10.0, y: CGFloat($0) * 10.0) } }.padding(.bottom, 70) } Group { Text("ZStack(alignment: .center)") ZStack(alignment: .center) { Rectangle() .fill(Color.red) .frame(width: 100, height: 50) Rectangle() .fill(Color.blue) .frame(width:50, height: 100) } .border(Color.green, width: 1) } Group { Text("ZStack(alignment: .bottomLeading)") ZStack(alignment: .bottomLeading) { Rectangle() .fill(Color.red) .frame(width: 100, height: 50) Rectangle() .fill(Color.blue) .frame(width:50, height: 100) } .border(Color.green, width: 1) } Group { Text("ZStack(alignment: .bottomTrailing)") ZStack(alignment: .bottomTrailing) { Rectangle() .fill(Color.red) .frame(width: 100, height: 50) Rectangle() .fill(Color.blue) .frame(width:50, height: 100) } .border(Color.green, width: 1) } Group { Text("ZStack(alignment: .topLeading)") ZStack(alignment: .topLeading) { Rectangle() .fill(Color.red) .frame(width: 100, height: 50) Rectangle() .fill(Color.blue) .frame(width:50, height: 100) } .border(Color.green, width: 1) } Group { Text("ZStack(alignment: .topTrailing)") ZStack(alignment: .topTrailing) { Rectangle() .fill(Color.red) .frame(width: 100, height: 50) Rectangle() .fill(Color.blue) .frame(width:50, height: 100) } .border(Color.green, width: 1) } Group { Text("ZStack(alignment: .top)") ZStack(alignment: .top) { Rectangle() .fill(Color.red) .frame(width: 100, height: 50) Rectangle() .fill(Color.blue) .frame(width:50, height: 100) } .border(Color.green, width: 1) } Group { Text("ZStack(alignment: .leading)") ZStack(alignment: .leading) { Rectangle() .fill(Color.red) .frame(width: 100, height: 50) Rectangle() .fill(Color.blue) .frame(width:50, height: 100) } .border(Color.green, width: 1) } Group { Text("ZStack(alignment: .trailing)") ZStack(alignment: .trailing) { Rectangle() .fill(Color.red) .frame(width: 100, height: 50) Rectangle() .fill(Color.blue) .frame(width:50, height: 100) } .border(Color.green, width: 1) } HStack var body: some View { VStack { Text("default") .frame(maxWidth: 400, alignment: .leading) .padding(.top, 20) HStack { Text("First") .border(Color.red, width: 2.0) Image(systemName: "2.circle") .resizable() .frame(width: 60, height: 60) .border(Color.green, width: 2.0) Text("Third Third Third Third Third") .border(Color.blue, width: 2.0) } .frame(width: 400) .border(Color.red) Text("alignment: .top") .frame(maxWidth: 400, alignment: .leading) .padding(.top, 20) HStack(alignment: .top) { Text("First") .border(Color.red, width: 2.0) Image(systemName: "2.circle") .resizable() .frame(width: 60, height: 60) .border(Color.green, width: 2.0) Text("Third Third Third Third Third") .border(Color.blue, width: 2.0) } .frame(width: 400) .border(Color.red) Text("alignment: .bottom") .frame(maxWidth: 400, alignment: .leading) .padding(.top, 20) HStack(alignment: .bottom) { Text("First") .border(Color.red, width: 2.0) Image(systemName: "2.circle") .resizable() .frame(width: 60, height: 60) .border(Color.green, width: 2.0) Text("Third Third Third Third Third") .border(Color.blue, width: 2.0) } .frame(width: 400) .border(Color.red) Text("spacing: 50") .frame(maxWidth: 400, alignment: .leading) .padding(.top, 20) HStack(spacing: 50) { Text("First") .border(Color.red, width: 2.0) Image(systemName: "2.circle") .resizable() .frame(width: 60, height: 60) .border(Color.green, width: 2.0) Text("Third Third Third Third Third") .border(Color.blue, width: 2.0) } .frame(width: 400) .border(Color.red) } } GeometryReader var body: some View { ScrollView(.vertical) { VStack { GeometryReader { geometry in Text(#""" geometry.frame(in: .global).origin.x: \#(geometry.frame(in: .global).origin.x) geometry.frame(in: .global).origin.y: \#(geometry.frame(in: .global).origin.y) geometry.frame(in: .global).width: \#(geometry.frame(in: .global).width) geometry.frame(in: .global).height: \#(geometry.frame(in: .global).height) geometry.frame(in: .global).minX: \#(geometry.frame(in: .global).minX) geometry.frame(in: .global).midX: \#(geometry.frame(in: .global).midX) geometry.frame(in: .global).maxX: \#(geometry.frame(in: .global).maxX) geometry.frame(in: .global).minY: \#(geometry.frame(in: .global).minY) geometry.frame(in: .global).midY: \#(geometry.frame(in: .global).midY) geometry.frame(in: .global).maxY: \#(geometry.frame(in: .global).maxY) """#) } .frame(height: 250) .border(Color.red, width: 2.0) GeometryReader { geometry in Text(#""" geometry.frame(in: .local).origin.x: \#(geometry.frame(in: .local).origin.x) geometry.frame(in: .local).origin.y: \#(geometry.frame(in: .local).origin.y) geometry.frame(in: .local).width: \#(geometry.frame(in: .local).width) geometry.frame(in: .local).height: \#(geometry.frame(in: .local).height) geometry.frame(in: .local).minX: \#(geometry.frame(in: .local).minX) geometry.frame(in: .local).midX: \#(geometry.frame(in: .local).midX) geometry.frame(in: .local).maxX: \#(geometry.frame(in: .local).maxX) geometry.frame(in: .local).minY: \#(geometry.frame(in: .local).minY) geometry.frame(in: .local).midY: \#(geometry.frame(in: .local).midY) geometry.frame(in: .local).maxY: \#(geometry.frame(in: .local).maxY) """#) } .frame(height: 250) .border(Color.green, width: 2.0) GeometryReader { geometry in Text(#""" geometry.size.debugDescription: \#(geometry.size.debugDescription) """#) } .frame(height: 100) .border(Color.green, width: 2.0) } } } LazyHGrid var rows1: [GridItem] = Array(repeating: .init(.fixed(40)), count: 2) var rows2: [GridItem] = Array(repeating: .init(.fixed(80)), count: 2) var rows3: [GridItem] = Array(repeating: .init(.fixed(40)), count: 4) var rows4: [GridItem] = Array(repeating: .init(.flexible(minimum: 40, maximum: 100)), count: 4) var rows5: [GridItem] = Array(repeating: .init(.adaptive(minimum: 40, maximum: 100)), count: 4) var body: some View { ScrollView(.vertical){ VStack { Group { Text("Array(repeating: .init(.fixed(40)), count: 2)") ScrollView(.horizontal) { LazyHGrid(rows: rows1, alignment: .top) { ForEach((0...79), id: \.self) { let codepoint = $0 + 0x1f600 let emoji = String(Character(UnicodeScalar(codepoint)!)) Text("\(emoji)") .font(.largeTitle) } } }.border(Color.red, width: 2) } Group { Text("Array(repeating: .init(.fixed(80)), count: 2)") ScrollView(.horizontal) { LazyHGrid(rows: rows2, alignment: .top) { ForEach((0...79), id: \.self) { let codepoint = $0 + 0x1f600 let emoji = String(Character(UnicodeScalar(codepoint)!)) Text("\(emoji)") .font(.largeTitle) } } }.border(Color.red, width: 2) } Group { Text("Array(repeating: .init(.fixed(40)), count: 4)") ScrollView(.horizontal) { LazyHGrid(rows: rows3, alignment: .top) { ForEach((0...79), id: \.self) { let codepoint = $0 + 0x1f600 let emoji = String(Character(UnicodeScalar(codepoint)!)) Text("\(emoji)") .font(.largeTitle) } } }.border(Color.red, width: 2) } Group { Text("Array(repeating: .init(.flexible(minimum: 40, maximum: 100)), count: 4)") ScrollView(.horizontal) { LazyHGrid(rows: rows4, alignment: .top) { ForEach((0...79), id: \.self) { let codepoint = $0 + 0x1f600 let emoji = String(Character(UnicodeScalar(codepoint)!)) Text("\(emoji)") .font(.largeTitle) } } }.border(Color.red, width: 2) } Group { Text("Array(repeating: .init(.adaptive(minimum: 40, maximum: 100)), count: 4)") ScrollView(.horizontal) { LazyHGrid(rows: rows5, alignment: .top) { ForEach((0...79), id: \.self) { let codepoint = $0 + 0x1f600 let emoji = String(Character(UnicodeScalar(codepoint)!)) Text("\(emoji)") .font(.largeTitle) } } }.border(Color.red, width: 2) } } } } LazyHStack @State private var showedIndex = "" @State private var showedIndex2 = "" var body: some View { VStack { Text("HStack") ScrollView(.horizontal) { HStack(alignment: .center, spacing: 10) { Section(header: Text("header"), footer: Text("footer")) { ForEach(1...20, id: \.self) { count in Image(systemName: "\(count).square") .font(.largeTitle) .frame(height: 70) .onAppear(perform: { showedIndex += "\(count) " }) } } } } Text("showedIndex: \(showedIndex)") .padding(.bottom, 50) Text("LazyHStack ") ScrollView(.horizontal) { LazyHStack(alignment: .center, spacing: 10) { Section(header: Text("header"), footer: Text("footer")) { ForEach(1...20, id: \.self) { count in Image(systemName: "\(count).square") .font(.largeTitle) .frame(height: 70) .onAppear(perform: { showedIndex2 += "\(count) " }) } } }.frame(height: 70) } Text("showedIndex: \(showedIndex2)") .padding(.bottom, 50) Text("LazyHStack pinnedViews: .sectionHeaders") ScrollView(.horizontal) { LazyHStack(alignment: .center, spacing: 10, pinnedViews: .sectionHeaders) { Section(header: Text("header"), footer: Text("footer")) { ForEach(1...20, id: \.self) { count in Image(systemName: "\(count).square") .font(.largeTitle) .frame(height: 70) } } }.frame(height: 70) } } } LazyVGrid var columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2) var columns2: [GridItem] = Array(repeating: .init(.fixed(80)), count: 2) var columns3: [GridItem] = Array(repeating: .init(.flexible(minimum: 80, maximum: 100)), count: 2) var columns4: [GridItem] = Array(repeating: .init(.flexible(minimum: 80, maximum: 100)), count: 4) var columns5: [GridItem] = Array(repeating: .init(.adaptive(minimum: 80)), count: 2) var body: some View { ScrollView(.vertical) { VStack { Group { Text("Array(repeating: .init(.flexible()), count: 2)") ScrollView { LazyVGrid(columns: columns) { ForEach((0...79), id: \.self) { let codepoint = $0 + 0x1f600 let codepointString = String(format: "%02X", codepoint) Text("\(codepointString)") let emoji = String(Character(UnicodeScalar(codepoint)!)) Text("\(emoji)") } }.font(.largeTitle) }.frame(height: 200) .border(Color.red, width: 2) } Group { Text("Array(repeating: .init(.fixed(80)), count: 2)") ScrollView { LazyVGrid(columns: columns2) { ForEach((0...79), id: \.self) { let codepoint = $0 + 0x1f600 let codepointString = String(format: "%02X", codepoint) Text("\(codepointString)") let emoji = String(Character(UnicodeScalar(codepoint)!)) Text("\(emoji)") } }.font(.largeTitle) }.frame(height: 200) .border(Color.red, width: 2) } Group { Text("Array(repeating: .init(.flexible(minimum: 80, maximum: 100)), count: 2)") ScrollView { LazyVGrid(columns: columns3) { ForEach((0...79), id: \.self) { let codepoint = $0 + 0x1f600 let codepointString = String(format: "%02X", codepoint) Text("\(codepointString)") let emoji = String(Character(UnicodeScalar(codepoint)!)) Text("\(emoji)") } }.font(.largeTitle) }.frame(height: 200) .border(Color.red, width: 2) } Group { Text("Array(repeating: .init(.flexible(minimum: 80, maximum: 100)), count: 4)") ScrollView { LazyVGrid(columns: columns4) { ForEach((0...79), id: \.self) { let codepoint = $0 + 0x1f600 let codepointString = String(format: "%02X", codepoint) Text("\(codepointString)") let emoji = String(Character(UnicodeScalar(codepoint)!)) Text("\(emoji)") } }.font(.largeTitle) }.frame(height: 200) .border(Color.red, width: 2) } Group { Text("Array(repeating: .init(.adaptive(minimum: 80)), count: 2)") ScrollView { LazyVGrid(columns: columns5) { ForEach((0...79), id: \.self) { let codepoint = $0 + 0x1f600 let codepointString = String(format: "%02X", codepoint) Text("\(codepointString)") let emoji = String(Character(UnicodeScalar(codepoint)!)) Text("\(emoji)") } }.font(.largeTitle) }.frame(height: 200) .border(Color.red, width: 2) } } } } LazyVStack @State private var showedIndex = "" @State private var showedIndex2 = "" var body: some View { ScrollView(.vertical) { VStack { Text("VStack") ScrollView(.vertical) { VStack(alignment: .center, spacing: 10) { Section(header: Text("header"), footer: Text("footer")) { ForEach(1...20, id: \.self) { count in Image(systemName: "\(count).square") .font(.largeTitle) .frame(height: 70) .onAppear(perform: { showedIndex += "\(count) " }) } } } }.frame(height: 200) Text("showedIndex: \(showedIndex)") .padding(.bottom, 50) Text("LazyVStack ") ScrollView(.vertical) { LazyVStack(alignment: .center, spacing: 10) { Section(header: Text("header"), footer: Text("footer")) { ForEach(1...20, id: \.self) { count in Image(systemName: "\(count).square") .font(.largeTitle) .frame(height: 70) .onAppear(perform: { showedIndex2 += "\(count) " }) } } } }.frame(height: 200) Text("showedIndex: \(showedIndex2)") .padding(.bottom, 50) Text("LazyVStack pinnedViews: .sectionHeaders") ScrollView(.vertical) { LazyVStack(alignment: .center, spacing: 10, pinnedViews: .sectionHeaders) { Section(header: Text("header"), footer: Text("footer")) { ForEach(1...20, id: \.self) { count in Image(systemName: "\(count).square") .font(.largeTitle) .frame(height: 70) } } } }.frame(height: 200) } } } ScrollViewReader ScrollViewReader { proxy in ScrollView { Button("proxy.scrollTo(30, anchor: .top)") { withAnimation { proxy.scrollTo(30, anchor: .top) } }.id(0) Button("proxy.scrollTo(30, anchor: .center)") { withAnimation { proxy.scrollTo(30, anchor: .center) } }.id(0) Button("proxy.scrollTo(30, anchor: .bottom)") { withAnimation { proxy.scrollTo(30, anchor: .bottom) } }.id(0) ForEach(1..<51) { index in Image(systemName: "\(index).square") .font(.largeTitle) .frame(height: 70) .id(index) } Button("Top") { withAnimation { proxy.scrollTo(0) } } } } VStack VStack { Text("default") .frame(maxWidth: 400, alignment: .leading) .padding(.top, 20) VStack { Text("First") .border(Color.red, width: 2.0) Image(systemName: "2.circle") .resizable() .frame(width: 60, height: 60) .border(Color.green, width: 2.0) Text("Third Third Third Third Third") .border(Color.blue, width: 2.0) } .frame(width: 400) .border(Color.red) Text("alignment: .leading") .frame(maxWidth: 400, alignment: .leading) .padding(.top, 20) VStack(alignment: .leading) { Text("First") .border(Color.red, width: 2.0) Image(systemName: "2.circle") .resizable() .frame(width: 60, height: 60) .border(Color.green, width: 2.0) Text("Third Third Third Third Third") .border(Color.blue, width: 2.0) } .frame(width: 400) .border(Color.red) Text("alignment: .center") .frame(maxWidth: 400, alignment: .leading) .padding(.top, 20) VStack(alignment: .center) { Text("First") .border(Color.red, width: 2.0) Image(systemName: "2.circle") .resizable() .frame(width: 60, height: 60) .border(Color.green, width: 2.0) Text("Third Third Third Third Third") .border(Color.blue, width: 2.0) } .frame(width: 400) .border(Color.red) Text("alignment: .trailing") .frame(maxWidth: 400, alignment: .leading) .padding(.top, 20) VStack(alignment: .trailing) { Text("First") .border(Color.red, width: 2.0) Image(systemName: "2.circle") .resizable() .frame(width: 60, height: 60) .border(Color.green, width: 2.0) Text("Third Third Third Third Third Third Third") .border(Color.blue, width: 2.0) } .frame(width: 400) .border(Color.red) } 最後に 最後まで閲覧いただきありがとうございました。 今後時間を見つけて残りのStructについてもまとめていく予定です。
  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

android studioで起こったpodエラーの対処

android studioにてflutter iosをrunすると以下のエラーが発生 The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. エラー内容を翻訳すると 「サンドボックスはPodfile.lockと同期していません。 「podinstall」を実行するか、CocoaPodsインストールを更新します。」 とのこと、、、 解決法 android studioのターミナルからプロジェクトディレクトリ/iosフォルダに行き podの存在を確認 $ pod outdated すると Updating spec repo `trunk` と表示されたのでpod自体はインストールされているようで $ pod update を実行! 自分の場合はpodファイルがあったので updateするとビルドに成功しました! もしpodがインストールされていなかった場合は $ pod install を実行してインストールしてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Flutter】Dartから入るオブジェクト指向プログラミング入門

Flutterからプログラミングを学び始めた人が、オブジェクト指向プログラミング(OOP)の考え方をさらっと学習するために本記事を執筆します。 間違いや分かりずらい点ありましたらご指摘お願いします サンプルプロジェクトの簡単な説明 Flutterプロジェクト作成時の初期アプリであるカウントアップアプリに機能追加したものになります。 カウンターの種類を変えることができ、0,1,2,・・・と進んでいき値が12になったらまた0に戻るHourCounterと単純に0から無限にカウントアップしていくSimpleCounterを左上のスイッチで切り替えることが出来ます。 ついでに、スイッチの状態は一度アプリを切っても保持されます(shared_preferencesを使用) カウンターのクラス図は以下のようになっています。 ※きちんとしたUMLの記法で書いていません。あくまでイメージとしてとらえてください。 以下にコードを置いています ※レクチャーの綴り間違えました...作り直すの面倒くさいのでそのまま... オブジェクト指向基礎 手続き型言語のコード量の多さや、保守性の悪さを解決する、プログラミング言語のアーキテクチャの一つ。 データと操作をまとめて一つのオブジェクトとしてとらえる。 オブジェクト指向の3大要素として「カプセル化、継承、ポリモーフィズム」がある。 クラスとメンバ クラスとはオブジェクトを生成するためにデータと処理を定義したもの。 データのことをフィールドといい、処理のことをメソッドという。 フィールドとメソッドのことをメンバという。メンバと言われたら、フィールドかメソッドのこと。 クラスを実際にオブジェクトにして使うことをインスタンス化と言い、生成されたものをインスタンスと呼ぶ。 Dartではnewをつけてもつけなくても良い。 class_example.dart class Hoge { // フィールド String value; // メソッド void fuga() { value = 'piyo'; } } アクセス修飾子 メンバにはアクセス制限をつけることが出来る。Javaのようにpublic、protected、privateのようなキーワードはなく、 Dartでは名前の頭に_(アンダースコア)をつけるとprivateとなる。 privateなメンバはクラス外からアクセスできない。(可視性の制御) counter_model.dart class CounterModel extends ChangeNotifier { ... // このcount自体はpublicなので外部から見える int get count => _counter?.counter; bool isChangedHourCounter = true; // _counterと_counterTypeは_がついているのでprivate CounterBase _counter; CounterType _counterType; ... その他の修飾子 var 通常の変数指定。型を指定しない場合につける。指定しない場合でもDartの場合型推論を行う。 個人的には基本使用せず、型を指定すべきかと思う。(型が決まっていない場合はdynamicを使うようにする) final 再代入不可となるが、参照先の内容は書き換えることが出来る。 finalの例 final String hoge = 'hoge'; hoge = 'fuga'; // NG final List<String> piyo = ['piyo','piyopiyo']; piyo[0] = 'piyopiyopiyo'; // OK 内容は書き換えられる const finalと似ているが、コンパイル時に値が決まっていることを保証する。 また、finalと違って参照先の内容を書き換えることも出来ない。 constのイメージ const String hoge = 'fuga'; print('hoge is ${hoge}'); // ↓コンパイル時に以下に変換されるイメージ print('hoge is fuga'); static 指定したクラス内のフィールドがインスタンスごとに保持されるのではなく、クラスで1つの実体を持つことを宣言する。 コンストラクタを用いたインスタンス化を行わなくても、ダイレクトに参照できるようになる。 staticの例 class Hoge { static String fuga = 'piyo'; } main() { print(Hoge.fuga); // piyo } 参考:Dartの変数定義時の修飾static/final/const、そしてconst constructorについて - Qiita カプセル化 アクセス修飾子を使って外部から見えるメンバを制限すること。 内部で何が行われているかを隠蔽し、使いやすくする。 コンストラクタ インスタンス化するときに呼ばれるメソッド。クラス内に定義しない場合、自動的に作成されたデフォルトコンストラクタが呼ばれる。 Dartでは他の言語のようにオーバーロード(同名で、受け取る値が異なるコンストラクタを複数定義し、インスタンス化するときに渡す変数を変えることで、生成されたオブジェクトを変化させる機能)は出来ないが、名前付きコンストラクタという機能で同じようなことが出来る。 コンストラクタの例 class CounterModel extends ChangeNotifier { // クラス名と同じ名前でコンストラクタを作る // メソッド全般の話だが、{}の中に引数を書くことで、使う時に変数名を参照できる // 下の例だと使う時、CounterModel(storageRepository: hoge)のようになる CounterModel({@required StorageRepositoryBase storageRepository}) : _storageRepository = storageRepository; // クラス内のフィールドをfinal(以降変更できない)とするときはコンストラクタ内で初期化するか // 宣言と同時に初期化する必要がある final StorageRepositoryBase _storageRepository; 以下は名前付きコンストラクタの例だが、_で始まっているのでprivateとなる 名前付きコンストラクタの例 class PersistenceStorageProvider { PersistenceStorageProvider._(); static final PersistenceStorageProvider instance = PersistenceStorageProvider._(); 抽象クラスとインターフェース いきなりDartでの仕様を学ぼうとすると混乱するので、一般的な話をする。 抽象クラスとはabstractというキーワードをclassキーワードの前につけたクラスのことで、継承されることを前提として設計される。 継承とはすでに存在しているクラスに、メンバを追加したり元々のメソッドを上書きしたりして新しいクラスを作ること。 継承元のクラスをスーパークラスと言い、継承して出来たクラスをサブクラスという。 抽象クラスは処理の再利用をしたいときに使う。 一方インターフェースは使えるメソッドを定義するためのもの。インターフェースを実装したクラスは、インターフェース内で定義されているメソッドがオーバーライドされていることが保証される。 参考:【詳解】抽象クラスとインタフェースを使いこなそう!! - Qiita Dartにおける抽象クラスと暗黙的インターフェース Dartではinterfaceといったキーワードは使えないが、代わりに全てのクラスは暗黙的に自動でinterfaceが定義される。 継承したいときはextendsを使い、実装したいとき(インターフェースとして使いたいとき)はimplementsを使用する。 abstractクラスをextendsした場合でもimplementsした場合でもスーパークラスで定義されているメソッドはすべてオーバーライドする必要がある。 違いとしては、extendsの場合、abstractクラスで定義したフィールドの利用が可能であり、メソッドの実装までabstract側でやっておいて、superとして処理を呼べる。(処理の共通化) counter_base.dart /// Counterの基本となる抽象クラス /// フィールドにはcounterを持ち /// incrementというcounterをインクリメントするメソッドの /// 実装を強制する abstract class CounterBase { int counter = 0; void increment(); } 以下が継承したサブクラス simple_counter.dart import 'package:flutter_oop_recture/domain/counter_base.dart'; class SimpleCounter extends CounterBase { // @override のアノテーションをつけてメソッドをオーバーライドする @override void increment() { // SimpleCounter自体のフィールドにcounterというものはないが // スーパークラスのフィールドを使用できる super.counter++; } } 一方implementsの場合、変数を定義できず、superとして処理も呼べないため、必ずメソッドの実装をオーバーライドして書き換える必要がある。 storag_repository_base.dart // abstract として定義しているが、目的はinterfaceとしての利用 abstract class StorageRepositoryBase { Future<void> savePersistenceStorage(String key, String value); Future<String> loadPersistenceStorage(String key); Future<bool> isExistKey(String key); Future<void> remove(String key); } 以下が実装クラス storage_repository.dart class StorageRepository implements StorageRepositoryBase { final PersistenceStorageProvider _instance = PersistenceStorageProvider.instance; @override Future<void> savePersistenceStorage(String key, String value) async { final SharedPreferences pref = await _instance.prefs; await pref.setString(key, value); } // 他すべてのメソッドをオーバーライド ... 参考:【Dart】abstract,mixin,extends,implements,with等の使い方基礎 - Zenn ポリモーフィズム ポリモーフィズム(多態性)とはクラスを継承してメソッドをオーバーライドしたことで、同名のメソッドを呼んだ時に挙動が変わることです。 先ほども紹介したCounterBaseの抽象クラスですが、incrementというメソッドを持っています。 counter_base.dart abstract class CounterBase { int counter = 0; void increment(); } SimpleCounterでは単純にインクリメントを行うだけに対し simple_counter.dart class SimpleCounter extends CounterBase { @override void increment() { super.counter++; } } HourCounterではincrementメソッドの中でresetメソッドを呼び、もし指定したmodular(法)の数になったら、値を0にリセットする処理を、スーパークラスのModularCounterで実装しています。 modular_counter.dart abstract class ModularCounter extends CounterBase { ModularCounter(this.modular); final int modular; /// ModularCounterにはリセット機能を実装するよう強制する /// スーパークラスのcounterを用いて実装しておく void reset() { // 明示的にsuper.counterと書かなくとも、スーパークラスのフィールドが使える if (counter >= modular) { counter = 0; } } } このModularCounterを継承することで、resetメソッドを呼び出すことが出来ます。 hour_counter.dart class HourCounter extends ModularCounter { HourCounter() : super(12); @override void increment() { super.counter++; reset(); } @override void reset() { // reset機能はスーパークラスの実装をそのまま使うため // 内部でsuper.reset()を呼ぶだけにしておく super.reset(); } } 依存関係逆転の原則 このようにextendsやimplementsを行って作ったクラスを使う時に注意すべきなのが依存関係逆転の原則です。 CounterBaseを継承して作られたSimpleCounterを使いたい場合に、SimpleCounterをそのまま宣言してしまうとSimpleCounterに依存してしまうことになります。 そうではなく、もっと抽象的な存在であるCounterBaseに依存させることで、後からHourCounterに差し替えることが可能になります。 counter_model.dart class CounterModel extends ChangeNotifier { ... CounterBase _counter; CounterType _counterType; ... /// _counterをCounterBaseとして定義しているので /// 後からSimpleCounterやHourCounterに差し替えることが出来る Future<void> switchCounter() async { if (isChangedHourCounter) { _counter = SimpleCounter(); isChangedHourCounter = false; await _storageRepository.savePersistenceStorage( key_counter, CounterType.simpleCounter.value); } else { _counter = HourCounter(); isChangedHourCounter = true; await _storageRepository.savePersistenceStorage( key_counter, CounterType.hourCounter.value); } notifyListeners(); } /// CounterBaseにはincrementメソッドを定義しているので、気にせずincrementを呼ぶことが出来る /// 実際に行われる処理は具体的に実装されたSimpleCounterやHourCounterで実装したincrementメソッド void increment() { _counter.increment(); notifyListeners(); } } 上記のケースでは単にカウンターの切替を行うだけですが、結局CounterModelがSimpleCounterとHourCounterに依存してしまっています。 そこで、このような依存関係をなくすために、外部から実装クラスを受け取るように実装したくなる時があります。 そのような手法をDI(Dependency Injection)と言います。 CounterModelではコンストラクタからStorageRepositoryBase(インターフェース)を受け取っており、内部ではどのような実装が行われているかは意識しません。 counter_model.dart class CounterModel extends ChangeNotifier { CounterModel({@required StorageRepositoryBase storageRepository}) : _storageRepository = storageRepository; final StorageRepositoryBase _storageRepository; これを外から代入する方法は様々ですが、FlutterではProviderパッケージを使って代入を行うことが出来ます。 main.dart void main() { runApp( MultiProvider( providers: [ // 後から読み込む場合の型はStorageRepositoryBase(インターフェース) Provider<StorageRepositoryBase>( // 実際に生成しているのはStorageRepository(StorageRepositoryのサブクラス) create: (BuildContext context) => StorageRepository(), ), ChangeNotifierProvider<CounterModel>( create: (BuildContext context) => CounterModel( // 上記で生成したStorageRepositoryBaseを型に持つ実体をCounterModelのコンストラクタへ代入 storageRepository: context.read<StorageRepositoryBase>()) ..init(), ), ], child: MyApp(), ), ); } 参考:オブジェクト指向 依存関係逆転の原則の「逆転」とは - Qiita シングルトン 生成したインスタンスが単一であることを保証するもの。 状態を保持したいときや、何度も同じ処理を行いたくないときに使う。 サンプルプロジェクトではshared_preferencesのインスタンスを提供するクラスをシングルトンで定義している。 persistence_storage_provider.dart class PersistenceStorageProvider { PersistenceStorageProvider._(); // staticとすることでインスタンスごとに保持されるのではなく、クラスで1つの実体を持つことを宣言する // instanceという変数への参照に自分自身を代入している static final PersistenceStorageProvider instance = PersistenceStorageProvider._(); // 内部で保持しておきたいインスタンス  SharedPreferences _prefs; Future<SharedPreferences> get prefs async { // ??=を使ってもし_prefがnullならば右辺の結果を代入し、そうでなければそのまま_prefを返却する return _prefs ??= await initSharedPreferences(); } // 実際にSharedPreferencesのインスタンスを取得する処理 Future<SharedPreferences> initSharedPreferences() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs; } } 参考: Dartの変数定義時の修飾static/final/const、そしてconst constructorについて - Qiita 【Flutter】Dartでシングルトンパターンを実装する - taketiyo.log おまけ Unit Test 依存関係逆転の法則を用いてCounterModelからSharedPreferencesの依存を排除したことによって、単体テストが書きやすくなる。単体テストの書き方については過去の記事を参照してください。 counter_model_test.dart void main() { // テスト用のメソッドを使いたいので、型はテスト用repositoryの方を指定する final StorageMemRepository storageRepository = StorageMemRepository(); group('init', () { test('shared_preferencesに値がない場合', () async { storageRepository.clear(); // ここでCounterModelを生成しているが、渡しているのは初めに定義したテスト用のインスタンス final model = CounterModel(storageRepository: storageRepository); await model.init(); // isChangeHourCounterは初期状態のtrueのままであること expect(model.isChangedHourCounter, true); model.dispose(); }); test('shared_preferencesにSimpleCounterが設定してある場合', () async { storageRepository.clear(); // あらかじめsimpleCounterの方をセットしておく await storageRepository.savePersistenceStorage( key_counter, CounterType.simpleCounter.value); final model = CounterModel(storageRepository: storageRepository); await model.init(); // isChangeHourCounterはfalseであること expect(model.isChangedHourCounter, false); model.dispose(); }); }); ... テスト用のレポジトリは以下のようにインメモリで動作するように記載している。 StorageRepositoryBaseをimplementsしているので、CounterModelのコンストラクタで代入出来る。 storage_mem_repository.dart class StorageMemRepository implements StorageRepositoryBase { // テスト用のレポジトリ内のデータの実態はただのMap final Map<String, String> _data = <String, String>{}; // データを初期化できるようにテスト用リポジトリだけclearメソッドを追加 void clear() { _data.clear(); } // その他のメソッドは実際にSharedPreferencesにアクセスせずに // 単に_dataを操作するように書く @override Future<bool> isExistKey(String key) { return Future<bool>.value(_data[key] != null); } ... } まとめ 書く中で色々調べなおして整理したりしたので自分自身の勉強にもなりました。 プログラミング初心者に向けてのつもりで書き始めたけど、初心者を抜け出して中級者になろうとしている人や中級者が読むといいのかもしれないと思いました。 参考 tokku5552/flutter_oop_recture - GitHub 【詳解】抽象クラスとインタフェースを使いこなそう!! - Qiita 【Dart】abstract,mixin,extends,implements,with等の使い方基礎 - Zenn オブジェクト指向 依存関係逆転の原則の「逆転」とは - Qiita Dartの変数定義時の修飾static/final/const、そしてconst constructorについて - Qiita 【Flutter】Dartでシングルトンパターンを実装する - taketiyo.log 【Flutter】Providerで最低限のDIを行ってテスタブルなコードにリファクタリングする - Qiita Flutterで単体テストを書く - Qiita Dartの変数定義時の修飾static/final/const、そしてconst constructorについて - Qiita
  • このエントリーをはてなブックマークに追加
  • 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で続きを読む