Created
July 25, 2020 14:40
-
-
Save olgusirman/9db9acbdf2682f0656f64a0a1dd78e34 to your computer and use it in GitHub Desktop.
Layout System sample from https://swiftbysundell.com/articles/swiftui-layout-system-guide-part-3/
This file contains hidden or 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 | |
struct ContentView: View { | |
var body: some View { | |
VStack { | |
EventHeader() | |
ImagePlaceholder().layoutPriority(-1) // added | |
.frame( minHeight: 100) | |
Text(makeDescription()) | |
//Text(makeDescription()).layoutPriority(1) // removed | |
Spacer() | |
//EventInfoList() | |
// EventInfoList().layoutPriority(1) // added | |
EventInfoList().fixedSize(horizontal: false, vertical: true) | |
}.padding() | |
} | |
} | |
extension ContentView { | |
func makeDescription() -> String { | |
String(repeating: "This is a description ", count: 50) | |
} | |
} | |
struct EventHeader: View { | |
var body: some View { | |
HStack(spacing: 15) { | |
CalendarView() | |
VStack(alignment: .leading) { | |
Text("Event title").font(.title) | |
Text("Location") | |
} | |
Spacer() | |
} | |
} | |
} | |
struct ImagePlaceholder: View { | |
var body: some View { | |
ZStack { | |
RoundedRectangle(cornerRadius: 10).stroke() | |
Text("Image placeholder") | |
} | |
} | |
} | |
struct EventInfoList: View { | |
var body: some View { | |
HeightSyncedRow(background: Color.secondary.cornerRadius(10)) { | |
EventInfoBadge( | |
iconName: "video.circle.fill", | |
text: "Video call available" | |
) | |
EventInfoBadge( | |
iconName: "doc.text.fill", | |
text: "Files are attached" | |
) | |
EventInfoBadge( | |
iconName: "person.crop.circle.badge.plus", | |
text: "Invites enabled, 5 people maximum" | |
) | |
} | |
} | |
} | |
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) | |
} | |
.padding(.vertical, 10) | |
.padding(.horizontal, 5) | |
} | |
} | |
struct CalendarView: View { | |
var eventIsVerified = true | |
var body: some View { | |
Image(systemName: "calendar") | |
.resizable() | |
.frame(width: 50, height: 50) | |
.padding() | |
.background(Color.red) | |
.cornerRadius(10) | |
.foregroundColor(.white) | |
.addVerifiedBadge(eventIsVerified) | |
} | |
} | |
private struct HeightPreferenceKey: PreferenceKey { | |
static let defaultValue: CGFloat = 0 | |
static func reduce(value: inout CGFloat, | |
nextValue: () -> CGFloat) { | |
value = nextValue() | |
} | |
} | |
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) | |
} | |
} | |
} | |
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 addVerifiedBadge(_ isVerified: Bool) -> some View { | |
ZStack(alignment: .topTrailing) { | |
self | |
if isVerified { | |
Image(systemName: "checkmark.circle.fill") | |
.alignAsBadge() | |
} | |
} | |
} | |
func alignAsBadge(withRatio ratio: CGFloat = 0.8, | |
alignment: Alignment = .topTrailing) -> some View { | |
alignmentGuide(alignment.horizontal) { | |
$0.width * ratio | |
} | |
.alignmentGuide(alignment.vertical) { | |
// Here we first align our view's bottom edge | |
// according to its host view's top edge, | |
// and we then subtract 80% of its height. | |
$0[.bottom] - $0.height * ratio | |
} | |
} | |
} | |
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