LayoutThatFits.swift (3233B)
1 // MIT License 2 // Copyright © Ryan Lintott 3 // https://github.com/ryanlintott/LayoutThatFits 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 // and associated documentation files (the "Software"), to deal in the Software without restriction, 7 // including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in all copies or 12 // substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 15 // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 // 20 import SwiftUI 21 22 @available(iOS 16.0, *) 23 struct LayoutThatFits: Layout { 24 let axes: Axis.Set 25 let layoutPreferences: [AnyLayout] 26 27 /// Creates a layout using the first layout that fits in the axes provided from the array of layout preferences. 28 /// - Parameters: 29 /// - axes: Axes this content must fit in. 30 /// - layoutPreferences: Layout preferences from largest to smallest. 31 init(in axes: Axis.Set = [.horizontal, .vertical], _ layoutPreferences: [any Layout]) { 32 self.axes = axes 33 self.layoutPreferences = layoutPreferences.map { AnyLayout($0) } 34 } 35 36 func layoutThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> AnyLayout? { 37 layoutPreferences.first(where: { layout in 38 var cache = layout.makeCache(subviews: subviews) 39 let size = layout.sizeThatFits(proposal: proposal, subviews: subviews, cache: &cache) 40 41 let widthFits = size.width <= (proposal.width ?? .infinity) 42 let heightFits = size.height <= (proposal.height ?? .infinity) 43 44 return (widthFits || !axes.contains(.horizontal)) && (heightFits || !axes.contains(.vertical)) 45 }) 46 } 47 48 func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { 49 guard let layout = layoutThatFits(proposal: proposal, subviews: subviews, cache: &cache) ?? layoutPreferences.last else { return CGSize(width: 10, height: 10) } 50 var cache = layout.makeCache(subviews: subviews) 51 return layout.sizeThatFits(proposal: proposal, subviews: subviews, cache: &cache) 52 } 53 54 func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { 55 guard let layout = layoutThatFits(proposal: proposal, subviews: subviews, cache: &cache) ?? layoutPreferences.last else { return } 56 var cache = layout.makeCache(subviews: subviews) 57 layout.placeSubviews(in: bounds, proposal: proposal, subviews: subviews, cache: &cache) 58 } 59 }