- 投稿日:2020-01-04T21:27:33+09:00
iOS アプリ開発で登場する View 実装方法の関係性 (SwiftUI、Storyboard、Xib、UIKit)
Swift で iOS アプリを開発するにあたり View の実装方法を調べていたところ、Storyboard、Xib、UIKit といった用語の関係性が全然分からず非常に苦労しました。
これらの全体的な関係性が (自分にとって) 分かりやすくまとまっている記事などが全く見つからなかったので、現時点での自分の理解をまとめます。
※ iOS 開発初心者のため、間違いあればご指摘ください
結論 (ざっくりした分類)
SwiftUI、Storyboard、Xib、UIKit をざっくり分類すると以下の通りです。
分類 要素名 GUI or コード コンポーネント化 概要 SwfitUI SwiftUI コード ◯ iOS 13 以上で使える新しい実装方法 SwfitUI 登場以前 Storyboard GUI × GUI でアプリの View 全体を管理 Xib GUI ◯ GUI で View のコンポーネントを作成 UIKit コード ◯ Swift のコードで View 周辺を扱うフレームワーク 以下でそれぞれ説明していきます。
SwiftUI vs SwiftUI 登場以前
まず大きな分類として、Swift UI と Swift UI 登場以前の 2 種類の実装方法があります。
Xcode の最近のバージョンで新規プロジェクトを作成しようとすると、User Interface の選択肢に SwiftUI と Storyboard が並んでいます。
この選択肢は、SwiftUI を使うか、それ以前の実装方法を使うかを選ぶのとかなり近いです。SwiftUI
2019 年に登場した、iOS 13 以降でのみ利用可能な新しい UI 実装方法です。
コードで UI を実装することになります。
ざっくり言うと、Flutter での UI 実装と似ていると言われています。現時点では細かい UI の再現は苦手な場合もあるようです。
なお、SwiftUI と SwiftUI 登場以前の実装方法 (UIKit) は共存可能とのことです。
参考
SwiftUI 登場以前
さて、ここが一番難しかったのですが、SwiftUI 登場以前の UI の実装方法には以下の 3 つがあります。
- Stobyboard (GUI)
- Xib (GUI)
- UIKit (コード)
これらからどれか 1 つを選んで実装するのかと思いきや、そうではありません。
UIKit 単独で View を実装することも可能ですが、Storyboard や Xib と組み合わせて実装することも多いようです。以下、これら 3 つを簡単に説明していきます。
Storyboard
GUI でアプリの各ページの View や画面遷移 (Segue) を実装するものです。
Storyboard だけで View を全て実装することができます。
View のコンポーネント化は苦手で、View 全体の管理に適しているようです。
View とプログラム (UIViewController) の紐付けは GUI の操作で実施します。Xib
GUI で各ページの View やコンポーネントを作る方法です。
Storyboard が苦手とする、View の再利用に適しています。
Xib で作成した View のコンポーネントは Storyboard やコードから利用可能です。ちなみに、Storyboard や Xib を編集するための Xcode のエディタを Interface Builder と言います。
参考
- StoryboardのBestPracticeについて考える(2) 〜ビューの再利用〜
- iOS 開発で storyboard と xib をうまく使い分けるプラクティス
- Storyboard よりも Xib を使いたい理由
- xibファイルでビューを作成して、ストーリーボードやコードから利用する方法(Swift3編)
UIKit
UIKit 自体は、SwiftUI 登場以前の iOS アプリの View 開発全般を担うフレームワークです。
UIKit は View の構築だけでなく、View とプログラムの紐付け (UIViewController) なども含んでいます。
なので、Storyboard や Xib で View を構築する場合であっても、プログラムとの紐付けのために UIKit を使うことになります。Storyboard や Xib でできることは UIKit で何でもできます。
「UIKit で View を構築するのは大変なので、Storyboard や Xib で代替する」というイメージです。UIKit だけで View を構築することを推奨している例もあります。
当然ではありますが、GUI (Storyboard や Xib) で View を構築する方が学習コストや初期実装コストが低いのに対し、UIKit のみで View を構築する方がメンテナンス性・再利用性が高まるのです。参考
- Interface Builderに依存しないiOS開発のススメ
- Storyboard派がコードでUIを実装するためのチュートリアル
- StoryboardやXibは使わない方が良いかも?
- iOSとコードベースレイアウト
まとめ
以上から、iOS の UI 実装には主に以下のパターンがあると言えます。1
- SwiftUI のみで実装する
- UIKit のみで実装する
- Storyboard + UIKit で実装する
- Xib + UIKit で実装する
- Storyboard + Xib + UIKit で実装する
どれを採用するか、各実装方法をどう使い分けるかは、アプリの特性や開発者の習熟度次第と思われます。
SwiftUI とそれ以外を組み合わせるパターンは調査不足のため除外しました。 ↩
- 投稿日:2020-01-04T21:27:33+09:00
iOS アプリ開発で登場する View 実装方法の関係 (SwiftUI、Storyboard、Xib、UIKit)
Swift で iOS アプリを開発するにあたり View の実装方法を調べていたところ、Storyboard、Xib、UIKit といった用語の関係性が全然分からず非常に苦労しました。
これらの全体的な関係性が (自分にとって) 分かりやすくまとまっている記事などが全く見つからなかったので、現時点での自分の理解をまとめます。
※ iOS 開発初心者のため、間違いあればご指摘ください
結論 (ざっくりした分類)
SwiftUI、Storyboard、Xib、UIKit をざっくり分類すると以下の通りです。
分類 要素名 GUI or コード コンポーネント化 概要 SwfitUI SwiftUI コード ◯ iOS 13 以上で使える新しい実装方法 SwfitUI 登場以前 Storyboard GUI × GUI でアプリの View 全体を管理 Xib GUI ◯ GUI で View のコンポーネントを作成 UIKit コード ◯ Swift のコードで View 周辺を扱うフレームワーク 以下でそれぞれ説明していきます。
SwiftUI vs SwiftUI 登場以前
まず大きな分類として、Swift UI と Swift UI 登場以前の 2 種類の実装方法があります。
Xcode の最近のバージョンで新規プロジェクトを作成しようとすると、User Interface の選択肢に SwiftUI と Storyboard が並んでいます。
この選択肢は、SwiftUI を使うか、それ以前の実装方法を使うかを選ぶのとかなり近いです。SwiftUI
2019 年に登場した、iOS 13 以降でのみ利用可能な新しい UI 実装方法です。
コードで UI を実装することになります。
ざっくり言うと、Flutter での UI 実装と似ていると言われています。現時点では細かい UI の再現は苦手な場合もあるようです。
なお、SwiftUI と SwiftUI 登場以前の実装方法 (UIKit) は共存可能とのことです。
参考
SwiftUI 登場以前
さて、ここが一番難しかったのですが、SwiftUI 登場以前の UI の実装方法には以下の 3 つがあります。
- Stobyboard (GUI)
- Xib (GUI)
- UIKit (コード)
これらからどれか 1 つを選んで実装するのかと思いきや、そうではありません。
UIKit 単独で View を実装することも可能ですが、Storyboard や Xib と組み合わせて実装することも多いようです。以下、これら 3 つを簡単に説明していきます。
Storyboard
GUI でアプリの各ページの View や画面遷移 (Segue) を実装するものです。
Storyboard だけで View を全て実装することができます。
View のコンポーネント化は苦手で、View 全体の管理に適しているようです。
View とプログラム (UIViewController) の紐付けは GUI の操作で実施します。Xib
GUI で各ページの View やコンポーネントを作る方法です。
Storyboard が苦手とする、View の再利用に適しています。
Xib で作成した View のコンポーネントは Storyboard やコードから利用可能です。ちなみに、Storyboard や Xib を編集するための Xcode のエディタを Interface Builder と言います。
参考
- StoryboardのBestPracticeについて考える(2) 〜ビューの再利用〜
- iOS 開発で storyboard と xib をうまく使い分けるプラクティス
- Storyboard よりも Xib を使いたい理由
- xibファイルでビューを作成して、ストーリーボードやコードから利用する方法(Swift3編)
UIKit
UIKit 自体は、SwiftUI 登場以前の iOS アプリの View 開発全般を担うフレームワークです。
UIKit は View の構築だけでなく、View とプログラムの紐付け (UIViewController) なども含んでいます。
なので、Storyboard や Xib で View を構築する場合であっても、プログラムとの紐付けのために UIKit を使うことになります。Storyboard や Xib でできることは UIKit で何でもできます。
「UIKit で View を構築するのは大変なので、Storyboard や Xib で代替する」というイメージです。UIKit だけで View を構築することを推奨している例もあります。
当然ではありますが、GUI (Storyboard や Xib) で View を構築する方が学習コストや初期実装コストが低いのに対し、UIKit のみで View を構築する方がメンテナンス性・再利用性が高まるのです。参考
- Interface Builderに依存しないiOS開発のススメ
- Storyboard派がコードでUIを実装するためのチュートリアル
- StoryboardやXibは使わない方が良いかも?
- iOSとコードベースレイアウト
まとめ
以上から、iOS の UI 実装には主に以下のパターンがあると言えます。1
- SwiftUI のみで実装する
- UIKit のみで実装する
- Storyboard + UIKit で実装する
- Xib + UIKit で実装する
- Storyboard + Xib + UIKit で実装する
どれを採用するか、各実装方法をどう使い分けるかは、アプリの特性や開発者の習熟度次第と思われます。
SwiftUI とそれ以外を組み合わせるパターンは調査不足のため除外しました。 ↩
- 投稿日:2020-01-04T21:27:33+09:00
iOS アプリの View 実装方法の関係 (SwiftUI、Storyboard、Xib、UIKit)
Swift で iOS アプリを開発するにあたり View の実装方法を調べていたところ、Storyboard、Xib、UIKit といった用語の関係が全然分からず非常に苦労しました。
これらの全体的な関係が (自分にとって) 分かりやすくまとまっている記事などが全く見つからなかったので、現時点での自分の理解をまとめます。
※ iOS 開発初心者のため、間違いあればご指摘ください
結論 (ざっくりした分類)
SwiftUI、Storyboard、Xib、UIKit をざっくり分類すると以下の通りです。
分類 要素名 GUI or コード コンポーネント化 概要 SwfitUI SwiftUI コード ◯ iOS 13 以上で使える新しい実装方法 SwfitUI 登場以前 Storyboard GUI × GUI でアプリの View 全体を管理 Xib GUI ◯ GUI で View のコンポーネントを作成 UIKit コード ◯ Swift のコードで View 周辺を扱うフレームワーク 以下でそれぞれ説明していきます。
SwiftUI vs SwiftUI 登場以前
まず大きな分類として、Swift UI と Swift UI 登場以前の 2 種類の実装方法があります。
Xcode の最近のバージョンで新規プロジェクトを作成しようとすると、User Interface の選択肢に SwiftUI と Storyboard が並んでいます。
この選択肢は、SwiftUI を使うか、それ以前の実装方法を使うかを選ぶのとかなり近いです。SwiftUI
2019 年に登場した、iOS 13 以降でのみ利用可能な新しい UI 実装方法です。
コードで UI を実装することになります。
ざっくり言うと、Flutter での UI 実装と似ていると言われています。現時点では細かい UI の再現は苦手な場合もあるようです。
なお、SwiftUI と SwiftUI 登場以前の実装方法 (UIKit) は共存可能とのことです。
参考
SwiftUI 登場以前
さて、ここが一番難しかったのですが、SwiftUI 登場以前の UI の実装方法には以下の 3 つがあります。
- Stobyboard (GUI)
- Xib (GUI)
- UIKit (コード)
これらからどれか 1 つを選んで実装するのかと思いきや、そうではありません。
UIKit 単独で View を実装することも可能ですが、Storyboard や Xib と組み合わせて実装することも多いようです。以下、これら 3 つを簡単に説明していきます。
Storyboard
GUI でアプリの各ページの View や画面遷移 (Segue) を実装するものです。
Storyboard だけで View を全て実装することができます。
View のコンポーネント化は苦手で、View 全体の管理に適しているようです。
View とプログラム (UIViewController) の紐付けは GUI の操作で実施します。Xib
GUI で各ページの View やコンポーネントを作る方法です。
Storyboard が苦手とする、View の再利用に適しています。
Xib で作成した View のコンポーネントは Storyboard やコードから利用可能です。ちなみに、Storyboard や Xib を編集するための Xcode のエディタを Interface Builder と言います。
参考
- StoryboardのBestPracticeについて考える(2) 〜ビューの再利用〜
- iOS 開発で storyboard と xib をうまく使い分けるプラクティス
- Storyboard よりも Xib を使いたい理由
- xibファイルでビューを作成して、ストーリーボードやコードから利用する方法(Swift3編)
UIKit
UIKit 自体は、SwiftUI 登場以前の iOS アプリの View 開発全般を担うフレームワークです。
UIKit は View の構築だけでなく、View とプログラムの紐付け (UIViewController) なども含んでいます。
なので、Storyboard や Xib で View を構築する場合であっても、プログラムとの紐付けのために UIKit を使うことになります。Storyboard や Xib でできることは UIKit で何でもできます。
「UIKit で View を構築するのは大変なので、Storyboard や Xib で代替する」というイメージです。UIKit だけで View を構築することを推奨している例もあります。
当然ではありますが、GUI (Storyboard や Xib) で View を構築する方が学習コストや初期実装コストが低いのに対し、UIKit のみで View を構築する方がメンテナンス性・再利用性が高まるのです。参考
- Interface Builderに依存しないiOS開発のススメ
- Storyboard派がコードでUIを実装するためのチュートリアル
- StoryboardやXibは使わない方が良いかも?
- iOSとコードベースレイアウト
まとめ
以上から、iOS の UI 実装には主に以下のパターンがあると言えます。1
- SwiftUI のみで実装する
- UIKit のみで実装する
- Storyboard + UIKit で実装する
- Xib + UIKit で実装する
- Storyboard + Xib + UIKit で実装する
どれを採用するか、各実装方法をどう使い分けるかは、アプリの特性や開発者の習熟度次第と思われます。
SwiftUI とそれ以外を組み合わせるパターンは調査不足のため除外しました。 ↩
- 投稿日:2020-01-04T21:05:53+09:00
SwiftUIでAVFoundationを使ってみた
はじめに
SwiftUIとAVFoundationをどうやったら組み合わせられるのかなっと考えてみました。
AVFoundationをObservableObjectにしたらカメラの画像を表示せずに顔検出とかできるのはないかと考えていました。
ということでまず作ってみました。作る上で必要なファイル一式
NewFile...で下記のファイルを新規作成してプロジェクトに追加してください
種別 ファイル名 概要 SwftUI View CALayerView CALayer用のView Swift File AVFoundationVM AVFoundationをObservableObject化するファイル 作り方
Step1.カメラのアクセス許可
Privacy - Camera Usage Descriptionを追加し利用用途を記載してください。
今回は、「カメラを利用します。」とします。
Step2.AVFoundationをObservableObject化
インスタンス生成時にAVFoundationの初期化処理を行い、
startSession()
endSession()
を呼び出すことでカメラの制御を開始終了します。takePhoto()
を呼び出すと画像をimage
に格納します。
image
は@Published
してありますので格納するとSwiftUI側で画像表示などに使えると思います。AVFoundationVM.swiftimport UIKit import Combine import AVFoundation class AVFoundationVM: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, ObservableObject { ///撮影した画像 @Published var image: UIImage? ///プレビュー用レイヤー var previewLayer:CALayer! ///撮影開始フラグ private var _takePhoto:Bool = false ///セッション private let captureSession = AVCaptureSession() ///撮影デバイス private var capturepDevice:AVCaptureDevice! override init() { super.init() prepareCamera() beginSession() } func takePhoto() { _takePhoto = true } private func prepareCamera() { captureSession.sessionPreset = .photo if let availableDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices.first { capturepDevice = availableDevice } } private func beginSession() { do { let captureDeviceInput = try AVCaptureDeviceInput(device: capturepDevice) captureSession.addInput(captureDeviceInput) } catch { print(error.localizedDescription) } let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) self.previewLayer = previewLayer let dataOutput = AVCaptureVideoDataOutput() dataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String:kCVPixelFormatType_32BGRA] if captureSession.canAddOutput(dataOutput) { captureSession.addOutput(dataOutput) } captureSession.commitConfiguration() let queue = DispatchQueue(label: "FromF.github.com.AVFoundationSwiftUI.AVFoundation") dataOutput.setSampleBufferDelegate(self, queue: queue) } func startSession() { if captureSession.isRunning { return } captureSession.startRunning() } func endSession() { if !captureSession.isRunning { return } captureSession.stopRunning() } // MARK: - AVCaptureVideoDataOutputSampleBufferDelegate func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { if _takePhoto { _takePhoto = false if let image = getImageFromSampleBuffer(buffer: sampleBuffer) { DispatchQueue.main.async { self.image = image } } } } private func getImageFromSampleBuffer (buffer: CMSampleBuffer) -> UIImage? { if let pixelBuffer = CMSampleBufferGetImageBuffer(buffer) { let ciImage = CIImage(cvPixelBuffer: pixelBuffer) let context = CIContext() let imageRect = CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer)) if let image = context.createCGImage(ciImage, from: imageRect) { return UIImage(cgImage: image, scale: UIScreen.main.scale, orientation: .right) } } return nil } }Step3.CALayerをSwiftUIで利用できるようにする
AVFoundationのカメラのライブビュー画像を表示できるように準備します。
CALayerView.swiftimport SwiftUI struct CALayerView: UIViewControllerRepresentable { var caLayer:CALayer func makeUIViewController(context: UIViewControllerRepresentableContext<CALayerView>) -> UIViewController { let viewController = UIViewController() viewController.view.layer.addSublayer(caLayer) caLayer.frame = viewController.view.layer.frame return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<CALayerView>) { caLayer.frame = uiViewController.view.layer.frame } }Step4.最後にSwiftUIを作成する
今回は、シャッターボタンを押すと撮影画像を表示するように作りました。
ContentView.swiftimport SwiftUI struct ContentView: View { @ObservedObject private var avFoundationVM = AVFoundationVM() var body: some View { VStack { if avFoundationVM.image == nil { Spacer() ZStack(alignment: .bottom) { CALayerView(caLayer: avFoundationVM.previewLayer) Button(action: { self.avFoundationVM.takePhoto() }) { Image(systemName: "camera.circle.fill") .renderingMode(.original) .resizable() .frame(width: 80, height: 80, alignment: .center) } .padding(.bottom, 100.0) }.onAppear { self.avFoundationVM.startSession() }.onDisappear { self.avFoundationVM.endSession() } Spacer() } else { ZStack(alignment: .topLeading) { VStack { Spacer() Image(uiImage: avFoundationVM.image!) .resizable() .scaledToFill() .aspectRatio(contentMode: .fit) Spacer() } Button(action: { self.avFoundationVM.image = nil }) { Image(systemName: "xmark.circle.fill") .renderingMode(.original) .resizable() .frame(width: 30, height: 30, alignment: .center) .foregroundColor(.white) .background(Color.gray) } .frame(width: 80, height: 80, alignment: .center) } } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { Group { ContentView() } } }参考情報
- 投稿日:2020-01-04T17:08:25+09:00
「リファクタリング 第2版」Swiftでコーディング その16
24頁 第1章 計算とフォーマットにフェーズ分割「フェーズの分割(p.160)」「関数の抽出(p.112)」
Swift版 main.swift
データ生成、結果表示付き。
import Foundation makeData() func playFor(aPerformance:Performance) -> Play { return plays[aPerformance.playID]! } func volumeCreditsFor(aPerformance:Performance) -> Int { var result = 0 result += max(aPerformance.audience - 30, 0) if "comedy" == playFor(aPerformance: aPerformance).type { result += Int(aPerformance.audience / 5) } return result } func usd(aNumber:Int) -> String { let format = NumberFormatter() format.numberStyle = .currency format.locale = Locale(identifier: "en_US") return format.string(from: NSNumber(value: aNumber / 100))! } func totalVolumeCredits(invoice:Invoice) -> Int { var result = 0 for perf in invoice.performances { result += volumeCreditsFor(aPerformance: perf) } return result } func totalAmount(invoice:Invoice) -> Int { var result = 0 for perf in invoice.performances { result += amountFor(aPerformance: perf) } return result } func statement(invoice:Invoice, plays:Dictionary<String, Play>) -> String { var result = "Statement for \(invoice.customer)\n" for perf in invoice.performances { result += " \(playFor(aPerformance: perf).name): " + usd(aNumber: amountFor(aPerformance: perf)) + " (\(perf.audience) seats)\n" } result += "Amount owed is " + usd(aNumber: totalAmount(invoice: invoice)) + "\n" result += "You earned \(totalVolumeCredits(invoice: invoice)) credits\n" return result } func amountFor(aPerformance:Performance) -> Int { var result = 0 switch playFor(aPerformance: aPerformance).type { case "tragedy": result = 40000 if aPerformance.audience > 30 { result += 1000 * (aPerformance.audience - 30) } case "comedy": result = 30000 if aPerformance.audience > 20 { result += 10000 + 500 * (aPerformance.audience - 20) } result += 300 * aPerformance.audience default: print("error") } return result } let result = statement(invoice: invoices[0], plays: plays) print(result)
- 投稿日:2020-01-04T15:59:40+09:00
Twitterの文字数チェックを組み込む
概要
iOSアプリへTwitterの投稿機能を持たせています。私が作った投稿機能はTwitterの部品ではなくアプリ独自で組み込みを行っているため、文字数チェックの組み込みが必要です。文字数チェックは単純に文字列をカウントするだけではTwitterの文字数と差異が発生するため、Twitterのオープンソースで提供されている文字数チェックのツール「twitter-text」を使用します。
環境
- macOS:10.14.6
- xcode:11.2.1
- swift:5.1.2
- iOS:13.0
- twitter-text:V3
準備
twitter-textをプロジェクトへインストール
podsファイルへtwitter-textを追記。
※CocoaPodsの導入について、事前にこちらのページを参照するといいですpodfile# Uncomment the next line to define a global platform for your project platform :ios, '13.0' target 'app' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! # Pods for MyTweetTool pod 'twitter-text' endターミナルからインストールを実施
# プロジェクトのフォルダへ移動 cd /Users/xxx/develop/app # インストール実施 pod installコーディング
import UIKit import twitter_text class MainView: UIViewController, SFSafariViewControllerDelegate, UITextViewDelegate { // ------------------------------ // MARK: controls // ------------------------------ let txtTweet = UITextView() let lblCharCounter = UILabel.init(frame: .zero) // ------------------------------ // MARK: override // ------------------------------ override func viewDidLoad() { setupUI() } // ------------------------------ // MARK: function // ------------------------------ func setupUI() { // 背景色の指定 self.view.backgroundColor = .white // ■ 投稿文章入力フィールド txtTweet.delegate = self txtTweet.frame = CGRect(x: 10, y: 100, width: getPosition(pPoint: widthSize, pAxis: .width), height: getPosition(pPoint: 0.4, pAxis: .height)) txtTweet.center = CGPoint(x: getPosition(pPoint: 0.5, pAxis: .width), y:getPosition(pPoint: 0.27, pAxis: .height) ) txtTweet.backgroundColor = UIColor.rgba(red: 224, green: 255, blue: 255, alpha: 1) // フォント設定 txtTweet.textColor = .black txtTweet.font = UIFont.systemFont(ofSize: 20) // テキストフィールドを角丸にする txtTweet.layer.borderWidth = 1 txtTweet.layer.cornerRadius = 10 txtTweet.layer.masksToBounds = true self.view.addSubview(txtTweet) // ■ 入力済み文字数のカウント lblCharCounter.frame = CGRect(x: 0,y: 0, width: 50, height: 50); lblCharCounter.backgroundColor = .clear lblCharCounter.textColor = .gray // フォント設定 lblCharCounter.textAlignment = .center lblCharCounter.text = String(format: "%@ char",defaultCounter.description) lblCharCounter.font = UIFont.systemFont(ofSize: 17) // サイズを調整してから配置する btnClearTweetText.sizeToFit() lblCharCounter.center = CGPoint(x: getPosition(pPoint: 0.7, pAxis: .width), y: getPosition(pPoint: 0.575, pAxis: .height)); self.view.addSubview(lblCharCounter) } // テキストビューの更新イベントを取得 func textViewDidChange(_ textView: UITextView) { // 入力文字数に応じた画面制御 let result: TwitterTextParseResults = getTweetCharactorCount(pCharactors: txtTweet.text) setDisplayByInputCharactor(pResult: result) } // 入力文字数取得(twitterのツールを使用して正確な文字数を取得する) func getTweetCharactorCount(pCharactors: String) -> TwitterTextParseResults { // 初期化 let parser = TwitterTextParser.init(configuration: TwitterTextConfiguration.init(fromJSONResource: kTwitterTextParserConfigurationV3)) // 文字列取得 let result: TwitterTextParseResults = parser.parseTweet(pCharactors) return result } /** 入力文字数による画面制御 */ func setDisplayByInputCharactor(pResult: TwitterTextParseResults) { lblCharCounter.text = String(format: "%@ char", pResult.weightedLength.description) lblCharCounter.sizeToFit() setPostEnabled(pValid: pResult.isValid) if pResult.isValid { lblCharCounter.textColor = .gray }else { lblCharCounter.textColor = .red } }参考ページ
- twitter-textのソースコード(github)
- twitterの文字数カウントに関わるドキュメント
おわりに
標準の部品を使うことが多いケースではドキュメントが少ないので苦労することもあると思います。特定の部分を特化させた機能を作るときに、少しでもお役にたてれば幸いです。
- 投稿日:2020-01-04T15:40:06+09:00
デコレーターをSwift5で実装する
※この記事は「全デザインパターンをSwift5で実装する」https://qiita.com/satoru_pripara/items/3aa80dab8e80052796c6 の一部です。
The Decorator(デコレータ)
0. デコレータの意義
デコレータは、あるクラスなどに元は無かった振る舞いを追加するデザインパターンである。元のクラスのコードをいじることなく振る舞いを柔軟に変えることができる。
また継承を行うのとは違って、プログラム実行時に動的にデコレータを使用し機能拡張することもできる。
注意点としては、元のクラスと全く関連性がないような機能をデコレータで付け加えるのはよろしくないという事である。単一のクラスは単一の機能・責務だけを持つべきという「単一責務の原則」に反し、分かりづらく保守しにくいコードになってしまう。
※単一責務の原則については下記などを参照
https://qiita.com/decoy0318/items/f201b725e91a4a1bb4cfSwiftではextensionを用いるか、デコレータクラスを新たに作る方法がある。
1. extensionを使う場合
extensionを使うと元のクラスにそのまま新しい機能を追加できるため非常に直感的で使いやすくなっている。
UIColorExtension.swiftimport UIKit public extension UIColor { //HTMLコード(#1411A4など)からUIColorインスタンスを作るイニシャライザを新設する convenience init(hex: UInt32) { let divisor = CGFloat(255) let red = CGFloat((hex & 0xFF0000) >> 16) / divisor let green = CGFloat((hex & 0x00FF00) >> 8) / divisor let blue = CGFloat(hex & 0x0000FF) / divisor self.init(red: red, green: green, blue: blue, alpha: 1) } }ただしextensionには制約があり、ストアドプロパティを新設するor既存のストアドプロパティの振る舞いを変更することはできない。
※ストアドプロパティについては下記参照
https://qiita.com/akeome/items/2197a635ac616ab2f8e2その場合には、下記のようにデコレータクラスを使うことになる。
2.デコレータクラスを使う場合
下記はUILabelにデコレータを使った例。
デコレータクラス自体が、目的のクラス(ここではUILabel)を継承する。
UILalbelDecorator.swiftimport UIKit public class BorderedLabelDecorator: UILabel {プロパティとしてもUILabelを保持する。
UILalbelDecorator.swiftfileprivate let wrappedLabel: UILabelUILabelの変数の振る舞いを変更していく。
UILalbelDecorator.swiftpublic required init(label: UILabel, cornerRadius: CGFloat = 3.0, borderWidth: CGFloat = 1.0, borderColor: UIColor = .black) { self.wrappedLabel = label super.init(frame: label.frame) self.wrappedLabel.layer.cornerRadius = cornerRadius self.wrappedLabel.layer.borderWidth = borderWidth self.wrappedLabel.layer.borderColor = borderColor.cgColor self.wrappedLabel.clipsToBounds = true } public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } //使用予定のあるUILabelの変数をオーバーライドし、wrappedLabelとBorderedLabelDecoratorの変数がリンクするようにする public override var textAlignment: NSTextAlignment { get { return self.wrappedLabel.textAlignment } set { self.wrappedLabel.textAlignment = newValue } } public override var backgroundColor: UIColor? { get { return self.wrappedLabel.backgroundColor } set { self.wrappedLabel.backgroundColor = newValue } } public override var textColor: UIColor? { get { return self.wrappedLabel.textColor } set { self.wrappedLabel.textColor = newValue } } public override var layer: CALayer { return self.wrappedLabel.layer } //元のストアドプロパティの振る舞いを変更する(文字をセットした時に顔文字を付け加える) public override var text: String? { get { return self.wrappedLabel.text } set { var str = "?" if let newVal = newValue { str += newVal + str } self.wrappedLabel.text = str } } }https://github.com/Satoru-PriChan/DecoratorUIColorUILabelDemo
参考文献: https://www.amazon.com/Design-Patterns-Swift-implement-Improve-ebook/dp/B07MDD3FQJ