はじめに

SwiftUI でタグを配置する際に詰まったため備忘録。

実現したいこと

↑ こういうやつ

詰まった部分

import SwiftUI

struct ContentView: View {
  @State var tagList = ["tagATagATagATagATagATagA", "tagBTagBTagBTagBTagBTagB", "tagCTagCTagCTagCTagCTagC", "tagDTagDTagDTagDTagDTagD"]

  var body: some View {
    VStack {
      Text("タグを横並びにしたいので HStack を使ってみると、各タグが全て改行されてしまい不自然")
       .padding(.vertical, 10)
      HStack {
        ForEach(tagList, id: \.self) { tag in
          Text(tag)
        }
      }
      Spacer()
    }
  }
}

解決方法

こちらの StackOverflow の記事が参考になった。

SwiftUI HStack with Wrap

import SwiftUI

struct ContentView: View {
  @State var tagList = ["tagATagATagATagA", "tagBTagBTagBTagB", "tagCTagCTagCTagC", "tagDTagDTagDTagDTagDTagD"]

  var body: some View {
    generateTags()
  }

  private func generateTags() -> some View {
    var width = CGFloat.zero
    var height = CGFloat.zero
    
    return ZStack(alignment: .topLeading) {
      ForEach(tagList, id: \.self) { tag in
        item(for: tag)
          .padding([.horizontal, .vertical], 4)
          .alignmentGuide(.leading, computeValue: { d in
            if abs(width - d.width) > 330 {
              width = 0
              height -= d.height
            }
            let result = width
            if tag == tagList.last {
              width = 0
            } else {
              width -= d.width
            }
            return result
          })
          .alignmentGuide(.top, computeValue: { _ in
            let result = height
            if tag == tagList.last {
              height = 0
            }
            return result
          })
      }
    }
  }
  
  func item(for text: String) -> some View {
    Text(text)
      .padding(.all, 5)
      .font(.footnote)
      .background(Color.blue)
      .foregroundColor(Color.white)
      .cornerRadius(30)
  }
}

alignmentGuide で 横幅を指定することで、タグを自動的に改行する。

元記事では GeometryReader を使って端末の画面サイズを指定していたが、他のコードに影響がありレイアウトが崩れたため、330にサイズを限定した。

また、実際にはサーバーのAPIからタグの配列を取得して表示させる。