You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI实现字符串数组自动换行并保留拖拽排序功能

SwiftUI可拖拽标签自动换行解决方案

问题

使用SwiftUI的draggable API展示游戏名称字符串数组时,所有标签被强制显示在同一行,无法根据容器宽度自动换行。

解决方案

方案1:使用LazyVGrid(iOS 14+)

将原布局中的HStack替换为LazyVGrid,通过自适应网格项实现自动换行。GridItem(.adaptive(minimum: 180))会根据可用宽度自动调整列数,当标签总宽度超过容器宽度时自动换行。

修改后的完整代码:

import SwiftUI

struct ContentView: View {
    
    @State private var games: [String] = ["Playstation Console", "Playstation Portal", "Steam Deck", "Xbox 360"]
    
    @State private var draggingItem: String?
    
    var body: some View {
        ScrollView {
            VStack(spacing: 36) {
                LazyVGrid(columns: [GridItem(.adaptive(minimum: 180), spacing: 12)], spacing: 16) {
                    ForEach(games, id: \.self) { game in
                        Text(game)
                            .fixedSize()
                            .padding(.horizontal)
                            .background(.gray.opacity(0.2))
                            .cornerRadius(16)
                            .font(.system(size: 24))
                            .draggable(game) {
                                Text(game)
                                    .border(.red)
                                    .onAppear {
                                        draggingItem = game
                                    }
                            }
                            .dropDestination(for: String.self) { items, location in
                                draggingItem = nil
                                return false
                            } isTargeted: { status in
                                if let draggingItem, status, draggingItem != game {
                                    if let sourceIndex = games.firstIndex(of: draggingItem),
                                       let destinationIndex = games.firstIndex(of: game) {
                                        withAnimation(.bouncy) {
                                            let sourceItem = games.remove(at: sourceIndex)
                                            games.insert(sourceItem, at: destinationIndex)
                                        }
                                    }
                                }
                            }
                    }
                }
                .padding(.horizontal, 12)
            }
            .padding(.top, 36)
        }
    }
}

#Preview {
    ContentView()
}

方案2:自定义FlowLayout(兼容iOS 13+)

如果需要兼容更早的iOS版本,可以实现自定义的FlowLayout,通过手动计算布局实现自动换行:

struct FlowLayout: Layout {
    var spacing: CGFloat = 10
    
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        let maxWidth = proposal.width ?? .infinity
        var currentWidth: CGFloat = 0
        var currentHeight: CGFloat = 0
        var lineHeight: CGFloat = 0
        
        for subview in subviews {
            let size = subview.sizeThatFits(.unspecified)
            if currentWidth + size.width + spacing > maxWidth {
                currentHeight += lineHeight + spacing
                currentWidth = size.width
                lineHeight = size.height
            } else {
                currentWidth += size.width + spacing
                lineHeight = max(lineHeight, size.height)
            }
        }
        currentHeight += lineHeight
        return CGSize(width: maxWidth, height: currentHeight)
    }
    
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        let maxWidth = bounds.width
        var currentX: CGFloat = bounds.minX
        var currentY: CGFloat = bounds.minY
        var lineHeight: CGFloat = 0
        
        for subview in subviews {
            let size = subview.sizeThatFits(.unspecified)
            if currentX + size.width + spacing > maxWidth {
                currentY += lineHeight + spacing
                currentX = bounds.minX
                lineHeight = size.height
            }
            subview.place(at: CGPoint(x: currentX, y: currentY), proposal: ProposedViewSize(size))
            currentX += size.width + spacing
            lineHeight = max(lineHeight, size.height)
        }
    }
}

使用时将HStack替换为FlowLayout即可:

FlowLayout(spacing: 12) {
    // 原ForEach内容不变
}
.padding(.horizontal, 12)

关键说明

  • 移除原布局中限制单行的HStack,替换为支持自动换行的容器
  • 保留Text的fixedSize()确保每个标签自身不换行
  • 调整spacing和minimum参数可以控制标签之间的间距和最小宽度,适配不同屏幕尺寸

内容的提问来源于stack exchange,提问作者Daryl Wong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 02:45:25