EqualIconWidthDomain.swift (4724B)
1 /* MIT License 2 * Copyright (c) 2021 rob mayoff 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * 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 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 */ 22 import SwiftUI 23 24 fileprivate struct IconWidthKey: PreferenceKey { 25 static var defaultValue: CGFloat? { nil } 26 27 static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) { 28 switch (value, nextValue()) { 29 case (nil, let next): value = next 30 case (_, nil): break 31 case (.some(let current), .some(let next)): value = max(current, next) 32 } 33 } 34 } 35 36 extension IconWidthKey: EnvironmentKey { } 37 38 extension EnvironmentValues { 39 fileprivate var iconWidth: CGFloat? { 40 get { self[IconWidthKey.self] } 41 set { self[IconWidthKey.self] = newValue } 42 } 43 } 44 45 fileprivate struct IconWidthModifier: ViewModifier { 46 @Environment(\.iconWidth) var width 47 48 func body(content: Content) -> some View { 49 content 50 .background(GeometryReader { proxy in 51 Color.clear 52 .preference(key: IconWidthKey.self, value: proxy.size.width) 53 }) 54 .frame(width: width) 55 } 56 } 57 58 struct EqualIconWidthLabelStyle: LabelStyle { 59 func makeBody(configuration: Configuration) -> some View { 60 HStack { 61 configuration.icon.modifier(IconWidthModifier()) 62 configuration.title //(alignment: .leading) 63 .multilineTextAlignment(.leading) 64 } 65 } 66 } 67 68 struct EqualIconWidthDomain<Content: View>: View { 69 let content: Content 70 @State var iconWidth: CGFloat? = nil 71 72 init(@ViewBuilder _ content: () -> Content) { 73 self.content = content() 74 } 75 76 var body: some View { 77 content 78 .environment(\.iconWidth, iconWidth) 79 .onPreferenceChange(IconWidthKey.self) { self.iconWidth = $0 } 80 .labelStyle(EqualIconWidthLabelStyle()) 81 } 82 } 83 // MARK: - 84 #if DEBUG 85 struct Demo1View: View { 86 var body: some View { 87 VStack(alignment: .leading) { 88 let people = "People" 89 let star = "Star" 90 let plane = "This is a plane" 91 VStack(alignment: .leading) { 92 Label(people, systemImage: "person.3") 93 Label(star, systemImage: "star") 94 Label(plane, systemImage: "airplane") 95 } 96 .padding() 97 EqualIconWidthDomain { 98 VStack(alignment: .leading) { 99 Label(people, systemImage: "person.3") 100 Label(star, systemImage: "star") 101 Label(plane, systemImage: "airplane") 102 } 103 } 104 } 105 } 106 } 107 108 struct Demo1_Previews: PreviewProvider { 109 static var previews: some View { 110 Demo1View() 111 } 112 } 113 114 115 struct FancyView: View { 116 var body: some View { 117 EqualIconWidthDomain { 118 VStack { 119 let people = "People" 120 let star = "Star" 121 let money = "Money" 122 Text(verbatim: "Le Menu") 123 .font(.caption) 124 Divider() 125 HStack { 126 VStack(alignment: .leading) { 127 Label( 128 title: { Text(verbatim: "Strawberry") }, 129 icon: { Text(verbatim: "🍓") }) 130 Label(money, systemImage: "banknote") 131 } 132 VStack(alignment: .leading) { 133 Label(people, systemImage: "person.3") 134 Label(star, systemImage: "star") 135 } 136 } 137 } 138 } 139 } 140 } 141 142 struct Demo2_Previews: PreviewProvider { 143 static var previews: some View { 144 FancyView() 145 } 146 } 147 #endif