Skip to content

Instantly share code, notes, and snippets.

@benben2019
Last active January 13, 2022 04:32
Show Gist options
  • Save benben2019/ab622d77fefcbb3f0ef359263882ef27 to your computer and use it in GitHub Desktop.
Save benben2019/ab622d77fefcbb3f0ef359263882ef27 to your computer and use it in GitHub Desktop.
SwiftUI layout example
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