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
相关产品推荐
相关产品推荐

