Skip to content

Latest commit

 

History

History
555 lines (468 loc) · 13.7 KB

README.md

File metadata and controls

555 lines (468 loc) · 13.7 KB

Places

Home

ContentView

import SwiftUI

enum Tab {
    case places
    case itineraries
    case profile
}

struct ContentView: View {
    @EnvironmentObject var modelData: ModelData
    @State var selection: Tab = .places

    var body: some View {
        TabView(selection: $selection) {
            NavigationStack {
                ScrollView {
                    ForEach(modelData.places) { place in
                        NavigationLink(value: place) {
                            PlaceCard(place: place)
                        }
                    }
                }
                .navigationTitle("Places")
                .navigationDestination(for: Place.self) { item in
                    PlaceDetail(place: place)
                }
            }
            .tabItem {
                Label("Places", systemImage: "rectangle.fill.on.rectangle.angled.fill")
            }
            .tag(Tab.places)

            Text("Itineraries")
                .tabItem {
                    Label("Itineraries", systemImage: "list.bullet")
                }
                .tag(Tab.itineraries)

            Text("Profile")
                .tabItem {
                    Label("Profile", systemImage: "person.fill")
                }
                .tag(Tab.profile)
        }
        .accentColor(appColor)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .preferredColorScheme(.dark)
            .environmentObject(ModelData())
    }
}

PlaceCard

import SwiftUI

struct PlaceCard: View {
    let place: Place

    var body: some View {
        VStack(spacing: 6) {
            HStack {
                Text(place.location)
                    .foregroundColor(.secondary)
                Spacer()
                Menu {
                    Button("Share", action: share)
                    Button("Save as PDF", action: saveAsPDF)
                } label: {
                    Image(systemName: "ellipsis.circle")
                        .tint(.primary)
                }
            }

            VStack(alignment: .leading, spacing: 10) {
                HStack {
                    Text(place.title)
                        .font(.title)
                    Spacer()
                }

                HStack {
                    Image(systemName: "map")
                    Text(place.subTitle)
                    Spacer()
                }

                Image(place.imageName)
                    .resizable()
                    .scaledToFit()
                    .cornerRadius(10)
            }
            .foregroundColor(.primary)
        }
        .padding()
        .background(.thinMaterial)
        .cornerRadius(10)
    }

    func share() {  }
    func saveAsPDF() {  }
}

struct PlaceCard_Previews: PreviewProvider {
    static var previews: some View {
        PlaceCard(place: place)
            .preferredColorScheme(.dark)
    }
}

PlaceDetail

import SwiftUI

struct PlaceDetail: View {
    let place: Place

    var body: some View {
        VStack {
            header
            title
            noteworthy
            Spacer()
        }
        .edgesIgnoringSafeArea(.top)
        .toolbar {
            toolBarItems
        }
        .tint(.primary)
    }

    var header: some View {
        Image("sf-detail")
            .resizable()
            .scaledToFit()
    }

    var title: some View {
        VStack(spacing: 6) {
            HStack {
                Text(place.location)
                    .foregroundColor(.secondary)
                Spacer()
            }

            VStack(alignment: .leading, spacing: 10) {
                HStack {
                    Text(place.title)
                        .font(.title)
                    Spacer()
                }

                HStack {
                    Image(systemName: "map")
                    Text(place.subTitle)
                    Spacer()
                }
            }
            .foregroundColor(.primary)
        }
        .padding()
    }

    var noteworthy: some View {
        List(place.noteworthy) { worthy in
            NavigationLink(value: worthy) {
                NoteworthyRow(noteworthy: worthy)
            }
            .navigationDestination(for: Noteworthy.self) { detail in
                NoteworthyDetail(noteworthy: detail)
            }
        }
        .scrollDisabled(true)
    }

    var toolBarItems: ToolbarItemGroup<TupleView<(Button<Image>, Button<Image>)>> {
        ToolbarItemGroup(placement: .navigationBarTrailing) {
            Button(action: {
                print("Share")
            }) {
                Image(systemName: "square.and.arrow.up")
            }

            Button(action: {
                print("Like")
            }) {
                Image(systemName: "heart")
            }
        }
    }
}

struct PlaceDetail_Previews: PreviewProvider {
    static var previews: some View {
        NavigationStack {
            PlaceDetail(place: place)
                .preferredColorScheme(.dark)
        }
    }
}

NoteworthyRow

import SwiftUI

struct NoteworthyRow: View {
    let noteworthy: Noteworthy
    let size: CGFloat = 48

    var body: some View {
        HStack {
            Image(systemName: noteworthy.iconName)
                .foregroundColor(appColor)
                .frame(width: size, height: size)
                .background(.ultraThinMaterial)
                .cornerRadius(10)

            VStack(alignment: .leading) {
                Text(noteworthy.title)
                    .font(.headline)
                Text(noteworthy.description)
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
        }
        .cornerRadius(10)
    }
}

struct NoteworthyRow_Previews: PreviewProvider {
    static var previews: some View {
        NoteworthyRow(noteworthy: noteworthy1)
            .preferredColorScheme(.dark)
    }
}

Itinerary

ItinerariesView

import SwiftUI

struct ItinerariesView: View {
    @EnvironmentObject var modelData: ModelData
    @State var showingAddItinerary = false

    var body: some View {
        NavigationStack {
            ScrollView {
                ForEach(modelData.itineraries) { itinerary in
                    NavigationLink(value: itinerary) {
                        ItineraryCard(itinerary: itinerary)
                            .padding()
                    }
                }
            }
            .toolbar {
                Button(action: {
                    self.showingAddItinerary.toggle()
                }) {
                    Image(systemName: "plus")
                }
                .foregroundColor(appColor)
            }
            .navigationTitle("Itineraries")
            .navigationDestination(for: Itinerary.self) { item in
                ItineraryDetail()
            }
            .fullScreenCover(isPresented: $showingAddItinerary) {
                AddItineraryView()
            }
        }
    }
}

ItineraryCard

struct ItineraryCard: View {
    let itinerary: Itinerary

    var body: some View {
        VStack(alignment: .leading) {
            Image(itinerary.imageName)
                .resizable()
                .scaledToFit()
            VStack(alignment: .leading) {
                Text(itinerary.subtitle)
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                Text(itinerary.title)
                    .font(.title)
                    .foregroundColor(.primary)
                Text(itinerary.friends)
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
            .padding()
        }
        .background(.thinMaterial)
        .cornerRadius(10)
    }
}

AddItineraryView

Because containers always want to fill the space provided to them, we can use frame() here to limit the height of each view.

struct AddItineraryView: View {
    var body: some View {
        NavigationStack {
            VStack(alignment: .leading) {
                TripDetailsView()
                    .frame(width: .infinity, height: 200)
                FriendDetails()
                    .frame(width: .infinity, height: 200)
                Spacer()
            }
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button("Add") {
                            print("Trailing")
                        }.foregroundColor(appColor)
                    }
                    ToolbarItem(placement: .navigationBarLeading) {
                        Button("Cancel") {
                            print("Pressed")
                        }.foregroundColor(appColor)
                    }
                }
                .navigationTitle("New Itinerary")
                .navigationBarTitleDisplayMode(.inline)
        }
    }
}

TripDetailsView

struct TripDetailsView: View {
    var body: some View {
        List {
            Section(header: Text("Trip Details")) {
                CityRow()
                DateRow(titleKey: "Start Date")
                DateRow(titleKey: "End Date")
            }
            .headerProminence(.increased)
        }
    }
}

CityRow

struct CityRow: View {
    var body: some View {
        HStack {
            Text("City")
            Spacer()
            Text("San Francisco")
                .foregroundColor(.secondary)
        }
    }
}

DateRow

struct DateRow: View {
    @State private var date = Date()
    let titleKey: String

    var body: some View {
        DatePicker(
            titleKey,
            selection: $date,
            displayedComponents: [.date]
        )
    }
}

FriendDetails

Here the chevoron gets added because we embed the FriendRow inside a NavigationLink. So no need to add manually.

struct FriendDetails: View {
    @State var showingAddFriend = false

    var body: some View {
        List {
            Section(header: Text("Friends")) {
                NavigationLink {
                    Text("Kate Grella")
                } label: {
                    FriendRow(fullname: "Kate Grella")
                }
                NavigationLink {
                    Text("Jeremy Sheldon")
                } label: {
                    FriendRow(fullname: "Jeremy Sheldon")
                }
                Button(action: {
                    self.showingAddFriend.toggle()
                }) {
                    AddFriendRow()
                }
                .foregroundColor(appColor)
            }
            .headerProminence(.increased)
        }
        .sheet(isPresented: $showingAddFriend) {
            Text("Add friend")
        }
    }
}

FriendRow

struct FriendRow: View {
    let fullname: String
    var body: some View {
        HStack {
            Image(systemName: "person")
            Text(fullname)
            Spacer()
        }
    }
}

AddFriendRow

No chevron added here.

struct AddFriendRow: View {
    var body: some View {
        HStack {
            Image(systemName: "plus.circle.fill")
            Text("Add People")
            Spacer()
        }
        .foregroundColor(appColor)
    }
}

Data

ModelData

import Foundation

let appColor = Color(red: 128/255, green: 165/255, blue: 117/255)

struct Place: Hashable, Identifiable {
    let id: UUID = UUID()
    let location: String
    let title: String
    let subtitle: String
    let imageName: String
    let description: String
    let noteworthy: [Noteworthy]
}

let description = "With strong cycling infrastructure and spectacular terrain just over the Golden Gate Bridge, cyclists enjoy one of the best bike-friendly cities."
let place = Place(location: "Colorado, US", title: "Aspen", subtitle: "14 Routes · Mountainous", imageName: "aspen", description: description, noteworthy: noteworthy)
let place2 = Place(location: "California, US", title: "San Francisco", subtitle: "12 Routes · Coastal", imageName: "sf", description: description, noteworthy: noteworthy)

struct Noteworthy: Hashable, Identifiable {
    let id = UUID()
    let title: String
    let description: String
    let iconName: String
}

let noteworthy1 = Noteworthy(title: "Carl the Fog", description: "Layering for rides is an art with the microclimages of the Bay. Here's tips from the locals.", iconName: "cloud.fog")
let noteworthy2 = Noteworthy(title: "Diverse Scenery", description: "From the Golden Gate bridge to the Redwoods, discover the most photographed places.", iconName: "binoculars")
let noteworthy3 = Noteworthy(title: "Hawk Hill", description: "Try beating these KOM's on this favorite local climb right over the Golden Gate bridge.", iconName: "mappin")
let noteworthy = [noteworthy1, noteworthy2, noteworthy3]

struct Itinerary: Hashable, Identifiable {
    let id = UUID()
    let title: String
    let subtitle: String
    let friends: String
    let imageName: String
}

let itinerary1 = Itinerary(title: "Santa Cruz", subtitle: "San Francisco · July 22-30, 2022", friends: "Kate, Jeremy, Dave, Matt, and 2 others", imageName: "itinerary1")
let itinerary2 = Itinerary(title: "Gamla Stan", subtitle: "Stockholm · August 15-30, 2022", friends: "Bjorne, Markus, Oskar, Ingrid and 3 others", imageName: "itinerary2")

class ModelData: ObservableObject {
    var places = [place, place2]
    var itineraries = [itinerary1, itinerary2]
}