Created
November 5, 2021 16:28
-
-
Save mayoff/260e019f307dfcb55f00e40a227a4225 to your computer and use it in GitHub Desktop.
demo code for laying out a SwiftUI table with fitted columns
This file contains 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 | |
fileprivate struct WidthPreferenceKey: PreferenceKey { | |
static var defaultValue: [AnyHashable: CGFloat] { [:] } | |
static func reduce(value: inout [AnyHashable : CGFloat], nextValue: () -> [AnyHashable : CGFloat]) { | |
value.merge(nextValue(), uniquingKeysWith: { max($0, $1) }) | |
} | |
} | |
extension WidthPreferenceKey: EnvironmentKey { } | |
extension EnvironmentValues { | |
fileprivate var widthPreference: WidthPreferenceKey.Value { | |
get { self[WidthPreferenceKey.self] } | |
set { self[WidthPreferenceKey.self] = newValue } | |
} | |
} | |
extension View { | |
public func widthPreference<Domain: Hashable>(_ key: Domain) -> some View { | |
return self.modifier(WidthPreferenceModifier(key: key)) | |
} | |
} | |
fileprivate struct WidthPreferenceModifier: ViewModifier { | |
@Environment(\.widthPreference) var widthPreference | |
var key: AnyHashable | |
func body(content: Content) -> some View { | |
content | |
.fixedSize(horizontal: true, vertical: false) | |
.background(GeometryReader { proxy in | |
Color.clear | |
.preference(key: WidthPreferenceKey.self, value: [key: proxy.size.width]) | |
}) | |
.frame(width: widthPreference[key]) | |
} | |
} | |
public struct WidthPreferenceDomain<Content: View>: View { | |
@State private var widthPreference: WidthPreferenceKey.Value = [:] | |
private var content: Content | |
public init(@ViewBuilder content: () -> Content) { | |
self.content = content() | |
} | |
public var body: some View { | |
content | |
.environment(\.widthPreference, widthPreference) | |
.onPreferenceChange(WidthPreferenceKey.self) { widthPreference = $0 } | |
} | |
} | |
struct ContentView: View { | |
var body: some View { | |
VStack(spacing: 0) { | |
ZStack { | |
TitleBackground() | |
Text("Clients") | |
.foregroundColor(.white) | |
} | |
TableView() | |
Spacer() | |
} | |
} | |
} | |
enum ColumnId: Hashable { | |
case name | |
case balance | |
case currency | |
} | |
struct TableView: View { | |
var body: some View { | |
WidthPreferenceDomain { | |
VStack(spacing: 0) { | |
HStack(spacing: 30) { | |
Text("Name") | |
.widthPreference(ColumnId.name) | |
Text("Balance") | |
.widthPreference(ColumnId.balance) | |
Text("Currency") | |
.widthPreference(ColumnId.currency) | |
Spacer() | |
} | |
.frame(maxWidth:.infinity) | |
.padding(12) | |
.background(Color(#colorLiteral(red: 0.9332349896, green: 0.9333916306, blue: 0.9332130551, alpha: 1))) | |
ForEach(0 ..< 4) { _ in | |
RowView() | |
} | |
} | |
} | |
} | |
} | |
struct RowView : View { | |
var body: some View { | |
HStack(spacing: 30) { | |
Text("John") | |
.widthPreference(ColumnId.name) | |
Text("$5300") | |
.widthPreference(ColumnId.balance) | |
Text("EUR") | |
.widthPreference(ColumnId.currency) | |
Spacer() | |
} | |
.padding(12) | |
} | |
} | |
struct TitleBackground: View { | |
var body: some View { | |
ZStack(alignment: .bottom){ | |
Rectangle() | |
.frame(maxWidth: .infinity, maxHeight: 60) | |
.foregroundColor(.red) | |
.cornerRadius(15) | |
//We only need the top corners rounded, so we embed another rectangle to the bottom. | |
Rectangle() | |
.frame(maxWidth: .infinity, maxHeight: 15) | |
.foregroundColor(.red) | |
} | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It works, but few suggestions
.fixedSize(horizontal: true, vertical: false)
that would make columns freely break into multiple lines.