【iOS】表示されているテキストをコピーできるUIを作成する簡単な方法

はじめに

iOSアプリを作成していると文言の表示に通常UILabelを使用します。 ただ、そのままではラベル上の文言のコピーを行うことができず、利便性の面であまり満足できないような場合があります。 以下で文言をコピーできるUIを作成する方法を記載します。

環境設定

以下の環境を使用しています。

  • Xcode10.2
  • Swift 5.0

実装

いくつか方法があるかと思いますが、簡単な方法としてUILabelの代わりにUITextViewを使用する方法があります。 基本的にはUILabelの代わりにUITextViewを設定するだけですが、注意点がいくつかあります。

  • isEditable を falseにする
    • trueのままだとタップで編集できてしまうためそれを防ぎます
  • isScrollEnabled を falseにする
    • このパラメータをfalseにすることで、UILabelのように内部の文言によってUITextViewのサイズが決定するようになります

また、表示する文言の行数を制限する場合下記のように設定します。

textView.textContainer.maximumNumberOfLines = 2;
textView.textContainer.lineBreakMode = .byTruncatingTail

Storyboard上では下記の赤枠の設定を変更します。行数制限についてはcode上で設定を行います。

f:id:Iganin:20190313175528p:plain

実際の画面表示は下記のようになります。該当のText部分をドラッグするとCopy, 検索, Shareの選択肢が表示されます。 ただ、左右のマージンを見ると赤背景の部分と、緑背景の部分でテキスト部分の表示に差があります。 ともに左右10ptのConstraintを親Viewに対してかけていますが、TextViewとUILabelはViewとTextContentとのPaddingなどに差があるためこのような表示の違いが生まれます。

f:id:Iganin:20190407214813p:plain

この部分は、下記の設定によって変更することができます。

  • textContainerInset
  • textContainer.lineFragmentPadding
  • layoutManager.usesFontLeading
        // textView内のInset
        textView.textContainerInset = .zero
        
        // TextView内の左右のPadding
        // defaultの値は5.0
        // contentsに使用できるwidthの計算に使用される
        textView.textContainer.lineFragmentPadding = 0.0
        
        // 通常はleadingがfontに依存して変わる
        // usesFontLeadingをfalseにすることでfontに依存しないようになる
        textView.layoutManager.usesFontLeading = false

ただ、上記の設定もコード上から行う必要があります。そのため、IB上からは設定の状況が確認できません。 そこで下記のように IBDesignableのクラスを作成し、使用することでIB上から確認できるようになります。

import Foundation
import UIKit

@IBDesignable
class UILabelTextView: UITextView {
    
    @IBInspectable
    var numberOfLines: Int = 1 {
        didSet {
            textContainer.maximumNumberOfLines = numberOfLines
        }
    }
    
    // IBInspectableにはEnumであるNSLineBreakModeを使用できなかったためIntを経由しています。
    // より良い方法をご存知の方がいましたら共有いただけますと幸いです。
    @IBInspectable
    var lineBreakModeNumber: Int = 0 {
        didSet {
            textContainer.lineBreakMode = NSLineBreakMode(rawValue: lineBreakModeNumber) ?? .byWordWrapping
        }
    }
    
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        commonInit()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        commonInit()
    }
    
    private func commonInit() {
        textContainerInset = .zero
        textContainer.lineFragmentPadding = 0.0
        layoutManager.usesFontLeading = false
    }
}

なお、IBDesignableのクラスを作成する際は、IBを開いた際のビルドスコープを狭めるためにUIパーツをEmbeded Frameworkに分離するのがおすすめです。

まとめ

UILabelのような扱いができるUITextViewを使用して表示文言のコピーができるUIを作成する方法をまとめました。 開発の中で上記要件が出た際はぜひ使用してみてください。

参考