20200104のSwiftに関する記事は7件です。

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 と言います。

参考

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 を構築する方がメンテナンス性・再利用性が高まるのです。

参考

まとめ

以上から、iOS の UI 実装には主に以下のパターンがあると言えます。1

  • SwiftUI のみで実装する
  • UIKit のみで実装する
  • Storyboard + UIKit で実装する
  • Xib + UIKit で実装する
  • Storyboard + Xib + UIKit で実装する

どれを採用するか、各実装方法をどう使い分けるかは、アプリの特性や開発者の習熟度次第と思われます。


  1. SwiftUI とそれ以外を組み合わせるパターンは調査不足のため除外しました。 

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

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 と言います。

参考

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 を構築する方がメンテナンス性・再利用性が高まるのです。

参考

まとめ

以上から、iOS の UI 実装には主に以下のパターンがあると言えます。1

  • SwiftUI のみで実装する
  • UIKit のみで実装する
  • Storyboard + UIKit で実装する
  • Xib + UIKit で実装する
  • Storyboard + Xib + UIKit で実装する

どれを採用するか、各実装方法をどう使い分けるかは、アプリの特性や開発者の習熟度次第と思われます。


  1. SwiftUI とそれ以外を組み合わせるパターンは調査不足のため除外しました。 

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

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 と言います。

参考

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 を構築する方がメンテナンス性・再利用性が高まるのです。

参考

まとめ

以上から、iOS の UI 実装には主に以下のパターンがあると言えます。1

  • SwiftUI のみで実装する
  • UIKit のみで実装する
  • Storyboard + UIKit で実装する
  • Xib + UIKit で実装する
  • Storyboard + Xib + UIKit で実装する

どれを採用するか、各実装方法をどう使い分けるかは、アプリの特性や開発者の習熟度次第と思われます。


  1. SwiftUI とそれ以外を組み合わせるパターンは調査不足のため除外しました。 

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

SwiftUIでAVFoundationを使ってみた

はじめに

SwiftUIとAVFoundationをどうやったら組み合わせられるのかなっと考えてみました。
AVFoundationをObservableObjectにしたらカメラの画像を表示せずに顔検出とかできるのはないかと考えていました。
ということでまず作ってみました。

作る上で必要なファイル一式

NewFile...で下記のファイルを新規作成してプロジェクトに追加してください

種別 ファイル名 概要
SwftUI View CALayerView CALayer用のView
Swift File AVFoundationVM AVFoundationをObservableObject化するファイル

作り方

Step1.カメラのアクセス許可

Privacy - Camera Usage Descriptionを追加し利用用途を記載してください。
今回は、「カメラを利用します。」とします。
InfoPlist.png

Step2.AVFoundationをObservableObject化

インスタンス生成時にAVFoundationの初期化処理を行い、startSession() endSession()を呼び出すことでカメラの制御を開始終了します。takePhoto()を呼び出すと画像をimageに格納します。
image@Publishedしてありますので格納するとSwiftUI側で画像表示などに使えると思います。

AVFoundationVM.swift
import 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.swift
import 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.swift
import 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()
        }
    }
}

参考情報

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

「リファクタリング 第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)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
        }

}

参考ページ

おわりに

標準の部品を使うことが多いケースではドキュメントが少ないので苦労することもあると思います。特定の部分を特化させた機能を作るときに、少しでもお役にたてれば幸いです。

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

デコレーターをSwift5で実装する

※この記事は「全デザインパターンをSwift5で実装する」https://qiita.com/satoru_pripara/items/3aa80dab8e80052796c6 の一部です。

The Decorator(デコレータ)

0. デコレータの意義

デコレータは、あるクラスなどに元は無かった振る舞いを追加するデザインパターンである。元のクラスのコードをいじることなく振る舞いを柔軟に変えることができる。

また継承を行うのとは違って、プログラム実行時に動的にデコレータを使用し機能拡張することもできる。

注意点としては、元のクラスと全く関連性がないような機能をデコレータで付け加えるのはよろしくないという事である。単一のクラスは単一の機能・責務だけを持つべきという「単一責務の原則」に反し、分かりづらく保守しにくいコードになってしまう。

※単一責務の原則については下記などを参照
https://qiita.com/decoy0318/items/f201b725e91a4a1bb4cf

Swiftではextensionを用いるか、デコレータクラスを新たに作る方法がある。

1. extensionを使う場合

extensionを使うと元のクラスにそのまま新しい機能を追加できるため非常に直感的で使いやすくなっている。

UIColorExtension.swift
import 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.swift
import UIKit

public class BorderedLabelDecorator: UILabel {

プロパティとしてもUILabelを保持する。

UILalbelDecorator.swift
fileprivate let wrappedLabel: UILabel

UILabelの変数の振る舞いを変更していく。

UILalbelDecorator.swift
    public 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

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