Last active
January 13, 2022 04:32
-
-
Save benben2019/ab622d77fefcbb3f0ef359263882ef27 to your computer and use it in GitHub Desktop.
SwiftUI layout example
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
struct LayoutExampleView: View { | |
var body: some View { | |
VStack { | |
EventHeader() | |
ImagePlaceholder() | |
// .layoutPriority(-1) | |
.frame(minHeight: 100) | |
Text(makeDescription()) | |
.layoutPriority(1) | |
Spacer() | |
BottomInfo() | |
// .layoutPriority(1) | |
.fixedSize(horizontal: false, vertical: true) | |
} | |
.padding() | |
} | |
} | |
struct CalendarView: View { | |
var body: some View { | |
Image(systemName: "calendar") | |
.resizable() | |
.frame(width: 50, height: 50) | |
.padding() | |
.background(Color.red) | |
.cornerRadius(10) | |
.foregroundColor(.white) | |
.addVerifiedBadge(true) | |
} | |
} | |
struct EventHeader: View { | |
var body: some View { | |
HStack(spacing: 15) { | |
CalendarView() | |
VStack(alignment: .leading) { | |
Text("Event title").font(.title) | |
Text("Location") | |
} | |
Spacer() | |
} | |
} | |
} | |
private extension LayoutExampleView { | |
func makeDescription() -> String { | |
String(repeating: "This is a description ", count: 50) | |
} | |
} | |
struct ImagePlaceholder: View { | |
var body: some View { | |
ZStack { | |
RoundedRectangle(cornerRadius: 10).stroke() | |
Text("Image placeholder") | |
} | |
} | |
} | |
struct BottomInfo: View { | |
var body: some View { | |
HeightSyncedRow(background: Color(UIColor.systemBackground)) { | |
EventInfoBadge( | |
iconName: "video.circle.fill", | |
text: "Video call available" | |
) | |
EventInfoBadge( | |
iconName: "doc.text.fill", | |
text: "Files are attached Files are attached Files are attached" | |
) | |
EventInfoBadge( | |
iconName: "person.crop.circle.badge.plus", | |
text: "Invites allowed" | |
) | |
} | |
.padding() | |
.background(Color.secondary) | |
.foregroundColor(.primary) | |
.cornerRadius(10) | |
} | |
} | |
struct EventInfoBadge: View { | |
var iconName: String | |
var text: String | |
var body: some View { | |
VStack { | |
Image(systemName: iconName) | |
.resizable() | |
.aspectRatio(contentMode: .fit) | |
.frame(width: 25, height: 25) | |
Text(text) | |
.frame(maxWidth: .infinity) | |
.multilineTextAlignment(.center) | |
// .lineLimit(4) | |
} | |
.padding(.vertical,10) | |
.padding(.horizontal,5) | |
.background(Color(UIColor.systemBackground)) | |
.cornerRadius(5) | |
} | |
} | |
struct HeightSyncedRow<Background: View, Content: View>: View { | |
private let background: Background | |
private let content: Content | |
@State private var childHeight: CGFloat? | |
init(background: Background, | |
@ViewBuilder content: () -> Content) { | |
self.background = background | |
self.content = content() | |
} | |
var body: some View { | |
HStack { | |
content.syncingHeightIfLarger(than: $childHeight) | |
.frame(height: childHeight) | |
.background(background) | |
} | |
} | |
} | |
extension View { | |
func syncingHeightIfLarger(than height: Binding<CGFloat?>) -> some View { | |
background(GeometryReader { proxy in | |
// We have to attach our preference assignment to | |
// some form of view, so we just use a clear color | |
// here to make that view completely transparent: | |
Color.clear.preference( | |
key: HeightPreferenceKey.self, | |
value: proxy.size.height | |
) | |
}) | |
.onPreferenceChange(HeightPreferenceKey.self) { | |
height.wrappedValue = max(height.wrappedValue ?? 0, $0) | |
} | |
} | |
func addVerifiedBadge(_ isVerified: Bool) -> some View { | |
ZStack(alignment: .topTrailing) { | |
self | |
if isVerified { | |
Image(systemName: "checkmark.circle.fill") | |
.foregroundColor(Color.primary) | |
.alignAsBadge() | |
} | |
} | |
} | |
func alignAsBadge(withRatio ratio: CGFloat = 0.8, | |
alignment: Alignment = .topTrailing) -> some View { | |
alignmentGuide(alignment.horizontal) { | |
$0.width * ratio | |
} | |
.alignmentGuide(alignment.vertical) { | |
$0[.bottom] - $0.height * ratio | |
} | |
} | |
} | |
private struct HeightPreferenceKey: PreferenceKey { | |
static let defaultValue: CGFloat = 0 | |
static func reduce(value: inout CGFloat, | |
nextValue: () -> CGFloat) { | |
value = nextValue() | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
LayoutExampleView() | |
.previewInterfaceOrientation(.portrait) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment