89 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			89 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
| //
 | |
| //  FlowLayout.swift
 | |
| //  WatchRunner Watch App
 | |
| //
 | |
| //  Created by LittleSheep on 2025/10/29.
 | |
| //
 | |
| 
 | |
| import SwiftUI
 | |
| 
 | |
| // MARK: - Custom Layouts
 | |
| 
 | |
| struct FlowLayout: Layout {
 | |
|     var alignment: HorizontalAlignment = .leading
 | |
|     var spacing: CGFloat = 10
 | |
| 
 | |
|     func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
 | |
|         let containerWidth = proposal.width ?? 0
 | |
|         let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
 | |
| 
 | |
|         var currentX: CGFloat = 0
 | |
|         var currentY: CGFloat = 0
 | |
|         var lineHeight: CGFloat = 0
 | |
|         var totalHeight: CGFloat = 0
 | |
| 
 | |
|         for size in sizes {
 | |
|             if currentX + size.width > containerWidth {
 | |
|                 // New line
 | |
|                 currentX = 0
 | |
|                 currentY += lineHeight + spacing
 | |
|                 totalHeight = currentY + size.height
 | |
|                 lineHeight = 0
 | |
|             }
 | |
| 
 | |
|             currentX += size.width + spacing
 | |
|             lineHeight = max(lineHeight, size.height)
 | |
|         }
 | |
|         totalHeight = currentY + lineHeight
 | |
| 
 | |
|         return CGSize(width: containerWidth, height: totalHeight)
 | |
|     }
 | |
| 
 | |
|     func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
 | |
|         let containerWidth = bounds.width
 | |
|         let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
 | |
| 
 | |
|         var currentX: CGFloat = 0
 | |
|         var currentY: CGFloat = 0
 | |
|         var lineHeight: CGFloat = 0
 | |
|         var lineElements: [(offset: Int, size: CGSize)] = []
 | |
| 
 | |
|         func placeLine() {
 | |
|             let lineWidth = lineElements.map { $0.size.width }.reduce(0, +) + CGFloat(lineElements.count - 1) * spacing
 | |
|             var startX: CGFloat = 0
 | |
|             switch alignment {
 | |
|             case .leading:
 | |
|                 startX = bounds.minX
 | |
|             case .center:
 | |
|                 startX = bounds.minX + (containerWidth - lineWidth) / 2
 | |
|             case .trailing:
 | |
|                 startX = bounds.maxX - lineWidth
 | |
|             default:
 | |
|                 startX = bounds.minX
 | |
|             }
 | |
| 
 | |
|             var xOffset = startX
 | |
|             for (offset, size) in lineElements {
 | |
|                 subviews[offset].place(at: CGPoint(x: xOffset, y: bounds.minY + currentY), proposal: ProposedViewSize(size)) // Use bounds.minY + currentY
 | |
|                 xOffset += size.width + spacing
 | |
|             }
 | |
|             lineElements.removeAll() // Clear elements for the next line
 | |
|         }
 | |
| 
 | |
|         for (offset, size) in sizes.enumerated() {
 | |
|             if currentX + size.width > containerWidth && !lineElements.isEmpty {
 | |
|                 // New line
 | |
|                 placeLine()
 | |
|                 currentX = 0
 | |
|                 currentY += lineHeight + spacing
 | |
|                 lineHeight = 0
 | |
|             }
 | |
| 
 | |
|             lineElements.append((offset, size))
 | |
|             currentX += size.width + spacing
 | |
|             lineHeight = max(lineHeight, size.height)
 | |
|         }
 | |
|         placeLine() // Place the last line
 | |
|     }
 | |
| }
 |