taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

commit b84cd21b301a5b6980458b3b2b3afa38898a7158
parent 4f1ab64e8be2a0bff51ab42b4099e8e372cd00be
Author: Marc Stibane <marc@taler.net>
Date:   Sun, 30 Mar 2025 21:40:40 +0200

LayoutThatFits

Diffstat:
MTalerWallet.xcodeproj/project.pbxproj | 6++++++
ATalerWallet1/Views/HelperViews/LayoutThatFits.swift | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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) + } +}