Last active
October 26, 2025 14:21
-
-
Save Koshimizu-Takehito/b6993e24c638b1ede25ffb0f6e1cc72a to your computer and use it in GitHub Desktop.
辺の比がフィボナッチ数の正方形になるように配置するLayoutプロトコル
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import SwiftUI | |
| struct FibonacciLayout: Layout { | |
| struct Cache { | |
| var fib: [Int128] | |
| } | |
| func makeCache(subviews: Subviews) -> Cache { | |
| switch subviews.count { | |
| case ...2: | |
| return Cache(fib: Array(repeating: 1, count: subviews.count)) | |
| default: | |
| var fib: [Int128] = [1, 1] | |
| for i in 2..<subviews.count { | |
| fib.append(fib[i - 1] + fib[i - 2]) | |
| } | |
| return Cache(fib: fib) | |
| } | |
| } | |
| func updateCache(_ cache: inout Cache, subviews: Subviews) { | |
| guard subviews.count - cache.fib.count > 0 else { | |
| return | |
| } | |
| for i in 0..<(subviews.count - cache.fib.count) { | |
| let c = cache.fib.count - 1 | |
| cache.fib.append(cache.fib[i + c] + cache.fib[i - 1 + c]) | |
| } | |
| } | |
| func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) -> CGSize { | |
| proposal.replacingUnspecifiedDimensions() | |
| } | |
| func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) { | |
| var cache = cache | |
| var subviews = subviews | |
| var bounds = bounds | |
| var count = 0 | |
| while !subviews.isEmpty { | |
| switch subviews.count { | |
| case 1: | |
| subviews[0].place(at: bounds.origin, proposal: ProposedViewSize(bounds.size)) | |
| default: | |
| let i = subviews.count-1 | |
| if count % 2 == 0 { | |
| var size = bounds.size | |
| size.height *= (CGFloat(cache.fib[i]) / CGFloat(cache.fib[i] + cache.fib[i-1])) | |
| if size.width <= size.height { | |
| size.height = size.width | |
| } else { | |
| size.width = size.height | |
| } | |
| if count == 0 { | |
| let h = size.height * (1 + CGFloat(cache.fib[i-1]) / CGFloat(cache.fib[i])) | |
| bounds.origin.y += (bounds.size.height - h)/2 | |
| } | |
| subviews.first?.place(at: bounds.origin, proposal: ProposedViewSize(size)) | |
| bounds.origin.y += size.height | |
| bounds.size.height -= size.height | |
| bounds.size.width = size.width | |
| } else { | |
| var size = bounds.size | |
| size.width *= (CGFloat(cache.fib[i]) / CGFloat(cache.fib[i] + cache.fib[i-1])) | |
| if size.height <= size.width { | |
| size.width = size.height | |
| } else { | |
| size.height = size.width | |
| } | |
| subviews.first?.place(at: bounds.origin, proposal: ProposedViewSize(size)) | |
| bounds.origin.x += size.width | |
| bounds.size.width -= size.width | |
| bounds.size.height = size.height | |
| } | |
| } | |
| cache.fib.removeLast() | |
| subviews = subviews[1..<subviews.count] | |
| count += 1 | |
| } | |
| } | |
| } | |
| struct ContentView: View { | |
| var numberOfItems = 3 | |
| var body: some View { | |
| FibonacciLayout { | |
| ForEach(0..<numberOfItems, id: \.self) { offset in | |
| color(at: offset) | |
| } | |
| } | |
| .padding() | |
| } | |
| func color(at index: Int) -> Color { | |
| let hue = (Double(index) * .pi * (8.0 / 7.0)).truncatingRemainder(dividingBy: 1.0) | |
| return Color(hue: hue, saturation: 0.6, brightness: 1.0) | |
| } | |
| } | |
| #Preview { | |
| @Previewable @State var numberOfItems: Int = 3 | |
| ContentView(numberOfItems: numberOfItems) | |
| .frame(maxHeight: .infinity) | |
| .overlay { | |
| VStack { | |
| Stepper("Count", value: $numberOfItems, in: 3...10) | |
| Button("Reset") { | |
| numberOfItems = 3 | |
| } | |
| .frame(maxWidth: .infinity, alignment: .trailing) | |
| } | |
| .frame(maxHeight: .infinity, alignment: .bottom) | |
| .padding() | |
| } | |
| .animation(.default, value: numberOfItems) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment