7.

Xcode でラベルを作りコードと連携する|IBOutlet と複数行表示

編集
この記事の要点
  • ラベルはコードから読み書きするだけなので、接続は @IBOutlet を使う(@IBAction ではない)
  • 文字を変えるのは label.text = "..."UI の更新はメインスレッドで行う
  • 複数行にするには numberOfLines = 0既定の 1 のままだと途中で切れる
  • 幅の制約が無いと折り返せない。制約と numberOfLines は必ずセット
  • SwiftUI なら Text(message)@State だけで自動的に再描画される

Storyboard で作る(UIKit)

1. ラベルを配置する

1. Main.storyboard を開く
2. Library(Cmd + Shift + L)から Label をドラッグして配置する
3. ダブルクリックして初期表示の文字を入れる
4. Attributes inspector でフォント・色・行数・配置を設定する
5. Add New Constraints で位置と幅の制約を付ける

2. コードとつなぐ

1. Assistant エディタで Storyboard とコードを並べる
2. ラベルを Control キーを押しながらコード側へドラッグする
3. Connection: Outlet   ← Action ではない
   Name:       messageLabel
   Type:       UILabel
4. Connect を押す
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var messageLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        messageLabel.text = "準備ができました"
    }

    @IBAction func tapButton(_ sender: UIButton) {
        messageLabel.text = "ボタンが押されました"
        messageLabel.textColor = .systemBlue
    }
}

ラベルは「操作を受け取る」部品ではないので @IBAction は作りません。Action にすると Touch Up Inside 相当のイベントが無く、接続しても何も起きません。

コードだけで作る

private let messageLabel: UILabel = {
    let label = UILabel()
    label.text = "準備ができました"
    label.font = .preferredFont(forTextStyle: .body)
    label.adjustsFontForContentSizeCategory = true   // 文字サイズ設定に追従する
    label.textColor = .label                         // ダークモードに追従する
    label.numberOfLines = 0                          // 何行でも折り返す
    label.textAlignment = .center
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(messageLabel)

    NSLayoutConstraint.activate([
        messageLabel.leadingAnchor.constraint(
            equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
        messageLabel.trailingAnchor.constraint(
            equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
        messageLabel.topAnchor.constraint(
            equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 24),
    ])
}

textColor.black ではなく .label を使うと、ダークモードで自動的に白になります。.systemBackground.secondaryLabel も同様です。

複数行にする

label.numberOfLines = 0        // 0 = 制限なし。既定は 1
label.numberOfLines = 2        // 2 行まで
label.lineBreakMode = .byTruncatingTail   // 入りきらないとき末尾を … にする
label.lineBreakMode = .byWordWrapping     // 単語単位で折り返す

// 行間を調整する
let style = NSMutableParagraphStyle()
style.lineSpacing = 6
label.attributedText = NSAttributedString(
    string: "1 行目\n2 行目",
    attributes: [.paragraphStyle: style])

numberOfLines = 0 にしても、幅の制約が無ければ折り返しません。ラベルは自分の幅が決まらないと改行位置を計算できないため、leadingtrailing(または width)の制約が必要です。

// 幅を優先して縦に伸ばす
label.setContentCompressionResistancePriority(.required, for: .vertical)
label.setContentHuggingPriority(.defaultLow, for: .horizontal)

// 1 行に収めたいときは自動縮小
label.numberOfLines = 1
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.7

値を流し込む

// 文字列補間
let name = "田中"
messageLabel.text = "\(name) さん、こんにちは"

// 数値の整形
let price = 1234567
messageLabel.text = price.formatted() + " 円"        // 1,234,567 円

// 日付の整形
messageLabel.text = Date().formatted(date: .long, time: .omitted)

// 一部だけ色を変える
let text = NSMutableAttributedString(string: "残り 3 件")
text.addAttribute(.foregroundColor, value: UIColor.systemRed,
                  range: NSRange(location: 3, length: 1))
messageLabel.attributedText = text

// nil を入れると何も表示されない("" と同じ扱い)
messageLabel.text = nil

非同期処理から更新する

// 落ちる: バックグラウンドスレッドから UI を触っている
URLSession.shared.dataTask(with: url) { data, _, _ in
    self.messageLabel.text = "取得しました"     // Main Thread Checker で警告
}.resume()

// 正しい: メインスレッドに戻す
URLSession.shared.dataTask(with: url) { data, _, _ in
    DispatchQueue.main.async {
        self.messageLabel.text = "取得しました"
    }
}.resume()

// async / await なら @MainActor を付ける
@MainActor
private func update(_ text: String) {
    messageLabel.text = text
}

Task {
    let (data, _) = try await URLSession.shared.data(from: url)
    await update("取得しました")
}

UI の更新は必ずメインスレッドで行います。守らないと表示が更新されない、レイアウトが崩れる、突然クラッシュするといった再現しにくい不具合になります。Xcode の Main Thread Checker が実行時に紫色の警告を出してくれます。

SwiftUI の場合

struct ContentView: View {
    @State private var message = "準備ができました"

    var body: some View {
        VStack(spacing: 16) {
            Text(message)
                .font(.body)
                .foregroundStyle(.primary)
                .multilineTextAlignment(.center)
                .lineLimit(nil)              // 折り返しの制限なし
                .padding(.horizontal)

            Button("更新") {
                message = "ボタンが押されました"   // 代入するだけで再描画される
            }
        }
    }
}

SwiftUI では @State の値が変わると自動的に画面が描き直されます@IBOutlet のような接続も、メインスレッドへ戻す記述も不要です。

表示されないときの確認順

症状原因
Unexpectedly found nil while unwrapping an Optional@IBOutlet が繋がっていない/viewDidLoad より前に触っている
文字が途中で切れるnumberOfLines が 1 のまま/幅の制約が無い
何も表示されないtextnil/文字色と背景色が同じ/他のビューの下にある
更新しても変わらないバックグラウンドスレッドから触っている
ダークモードで読めない色を .black で固定している。.label を使う
Storyboard の文字が実行時に消えるviewDidLoadtext を上書きしている

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. マップの追加方法
  2. テンプレートの種類一覧
  3. ファイルの役割一覧
  4. シミュレーターの画面の拡大/縮小をする方法
  5. スライダーの作成とカスタマイズ
  6. ボタンの作成とプログラムと連携
  7. ラベルの作成とプログラムと連携
  8. 【Xcode/Swift】ImageViewのContentMode一覧
  9. エラー一覧
  10. アプリを実機で起動させる方法
  11. ツールバーの設置とボタンの追加
  12. 画像の追加

最近更新/作成されたページ