commit b84cd21b301a5b6980458b3b2b3afa38898a7158
parent 4f1ab64e8be2a0bff51ab42b4099e8e372cd00be
Author: Marc Stibane <marc@taler.net>
Date: Sun, 30 Mar 2025 21:40:40 +0200
LayoutThatFits
Diffstat:
2 files changed, 65 insertions(+), 0 deletions(-)
diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj
@@ -198,6 +198,8 @@
4E847B802C9030E0003A164E /* TabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E847B7E2C9030E0003A164E /* TabBarView.swift */; };
4E847B822C9065FD003A164E /* ScopePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E847B812C9065FD003A164E /* ScopePicker.swift */; };
4E847B832C9065FD003A164E /* ScopePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E847B812C9065FD003A164E /* ScopePicker.swift */; };
+ 4E84D7B72D96A57900D2B1CB /* LayoutThatFits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E84D7B62D96A57800D2B1CB /* LayoutThatFits.swift */; };
+ 4E84D7B82D96A57900D2B1CB /* LayoutThatFits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E84D7B62D96A57800D2B1CB /* LayoutThatFits.swift */; };
4E87C8732A31CB7F001C6406 /* TransactionsEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E87C8722A31CB7F001C6406 /* TransactionsEmptyView.swift */; };
4E8C17202A6509BB005B2392 /* Atkinson-Hyperlegible-Regular-102.otf in Resources */ = {isa = PBXBuildFile; fileRef = 4E8C171C2A6509BB005B2392 /* Atkinson-Hyperlegible-Regular-102.otf */; };
4E8C17212A6509BB005B2392 /* Atkinson-Hyperlegible-Italic-102.otf in Resources */ = {isa = PBXBuildFile; fileRef = 4E8C171D2A6509BB005B2392 /* Atkinson-Hyperlegible-Italic-102.otf */; };
@@ -451,6 +453,7 @@
4E7F85162D63185E00954C30 /* Environment+EdgeInsets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Environment+EdgeInsets.swift"; sourceTree = "<group>"; };
4E847B7E2C9030E0003A164E /* TabBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = "<group>"; };
4E847B812C9065FD003A164E /* ScopePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScopePicker.swift; sourceTree = "<group>"; };
+ 4E84D7B62D96A57800D2B1CB /* LayoutThatFits.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutThatFits.swift; sourceTree = "<group>"; };
4E87C8722A31CB7F001C6406 /* TransactionsEmptyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionsEmptyView.swift; sourceTree = "<group>"; };
4E8C171C2A6509BB005B2392 /* Atkinson-Hyperlegible-Regular-102.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Atkinson-Hyperlegible-Regular-102.otf"; sourceTree = "<group>"; };
4E8C171D2A6509BB005B2392 /* Atkinson-Hyperlegible-Italic-102.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Atkinson-Hyperlegible-Italic-102.otf"; sourceTree = "<group>"; };
@@ -934,6 +937,7 @@
4E5D2C8A2C574CB0003F7A49 /* GradientBorder.swift */,
4E0A71172C3AB099002485BB /* IconBadge.swift */,
4EB095432989CBFE0043A8A1 /* LaunchAnimationView.swift */,
+ 4E84D7B62D96A57800D2B1CB /* LayoutThatFits.swift */,
4E6EDD862A363D8D0031D520 /* ListStyle.swift */,
4EB0954A2989CBFE0043A8A1 /* LoadingView.swift */,
4E4A3F0A2CD4B6CD00CA6A90 /* NavLink.swift */,
@@ -1420,6 +1424,7 @@
4E5349462D997BDF00FA55D0 /* CGSize+Random.swift in Sources */,
4E3EAE6E2A990778009F1BE8 /* Model+P2P.swift in Sources */,
4E0A71182C3AB099002485BB /* IconBadge.swift in Sources */,
+ 4E84D7B72D96A57900D2B1CB /* LayoutThatFits.swift in Sources */,
4E3EAE6F2A990778009F1BE8 /* TalerStrings.swift in Sources */,
4E3EAE702A990778009F1BE8 /* CurrencyInputView.swift in Sources */,
4EE9E1FD2D7E3C8000365E72 /* UserDefaults+oimMode.swift in Sources */,
@@ -1570,6 +1575,7 @@
4E5349472D997BDF00FA55D0 /* CGSize+Random.swift in Sources */,
4ECB62802A0BA6DF004ABBB7 /* Model+P2P.swift in Sources */,
4E0A71192C3AB099002485BB /* IconBadge.swift in Sources */,
+ 4E84D7B82D96A57900D2B1CB /* LayoutThatFits.swift in Sources */,
4EB0950A2989CB7C0043A8A1 /* TalerStrings.swift in Sources */,
4EA551252A2C923600FEC9A8 /* CurrencyInputView.swift in Sources */,
4EE9E1FE2D7E3C8000365E72 /* UserDefaults+oimMode.swift in Sources */,
diff --git a/TalerWallet1/Views/HelperViews/LayoutThatFits.swift b/TalerWallet1/Views/HelperViews/LayoutThatFits.swift
@@ -0,0 +1,59 @@
+// MIT License
+// Copyright © Ryan Lintott
+// https://github.com/ryanlintott/LayoutThatFits
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+// and associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+import SwiftUI
+
+@available(iOS 16.0, *)
+struct LayoutThatFits: Layout {
+ let axes: Axis.Set
+ let layoutPreferences: [AnyLayout]
+
+ /// Creates a layout using the first layout that fits in the axes provided from the array of layout preferences.
+ /// - Parameters:
+ /// - axes: Axes this content must fit in.
+ /// - layoutPreferences: Layout preferences from largest to smallest.
+ init(in axes: Axis.Set = [.horizontal, .vertical], _ layoutPreferences: [any Layout]) {
+ self.axes = axes
+ self.layoutPreferences = layoutPreferences.map { AnyLayout($0) }
+ }
+
+ func layoutThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> AnyLayout? {
+ layoutPreferences.first(where: { layout in
+ var cache = layout.makeCache(subviews: subviews)
+ let size = layout.sizeThatFits(proposal: proposal, subviews: subviews, cache: &cache)
+
+ let widthFits = size.width <= (proposal.width ?? .infinity)
+ let heightFits = size.height <= (proposal.height ?? .infinity)
+
+ return (widthFits || !axes.contains(.horizontal)) && (heightFits || !axes.contains(.vertical))
+ })
+ }
+
+ func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
+ guard let layout = layoutThatFits(proposal: proposal, subviews: subviews, cache: &cache) ?? layoutPreferences.last else { return CGSize(width: 10, height: 10) }
+ var cache = layout.makeCache(subviews: subviews)
+ return layout.sizeThatFits(proposal: proposal, subviews: subviews, cache: &cache)
+ }
+
+ func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
+ guard let layout = layoutThatFits(proposal: proposal, subviews: subviews, cache: &cache) ?? layoutPreferences.last else { return }
+ var cache = layout.makeCache(subviews: subviews)
+ layout.placeSubviews(in: bounds, proposal: proposal, subviews: subviews, cache: &cache)
+ }
+}