Coder Social home page Coder Social logo

airbnb-clone's Introduction

Airbnb Clone

Youtube

๐Ÿ“ฑ View

NavigationStack

NavigationStack {
    ScrollView {
        SearchAndFilterBar()
            .onTapGesture {
                withAnimation(.easeIn) {
                    showDestinationSearchView.toggle()
                }
            }
            
        LazyVStack(spacing: 32) {
            ForEach(viewModel.listings) { listing in
                NavigationLink(value: listing) {
                    ListingItemView(listing: listing)
                        .frame(height: 400)
                        .clipShape(RoundedRectangle(cornerRadius: 10))
                }
            }
        }
    }
    .navigationDestination(for: Listing.self) { listing in
        ListingDetailView(listing: listing)
            .navigationBarBackButtonHidden()
    }
    
}

NavigationLink ์— Hashable ํ•œ value๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด .navigationDestination ๋ฉ”์†Œ๋“œ๋กœ ๊ฐ’์„ ๋ฐ›์•„์™€ ๊ฐ’์— ๋”ฐ๋ผ ์›ํ•˜๋Š” ๋ทฐ๋ฅผ ๊ทธ๋ ค์ค„ ์ˆ˜ ์žˆ๋‹ค.

LazyVStack

LazyVStack(spacing: 32) {
    ForEach(viewModel.listings) { listing in
        NavigationLink(value: listing) {
            ListingItemView(listing: listing)
                .frame(height: 400)
                .clipShape(RoundedRectangle(cornerRadius: 10))
        }
    }
}

item์ด ํ™”๋ฉด์— ๋ Œ๋”๋ง ๋  ๋•Œ ์ƒ์„ฑ๋œ๋‹ค.

TapView

TabView {
    ExploreView()
        .tabItem { Label("Explore", systemImage: "magnifyingglass") }
    WishlistView()
        .tabItem { Label("Wishlists", systemImage: "heart") }
    ProfileView()
        .tabItem { Label("Profile", systemImage: "person") }

}
TabView {
    ForEach(listing.imageUrls, id: \.self) { image in
        Image(image)
            .resizable()
            .scaledToFill()
    }
}
.tabViewStyle(.page)

MapKit

import MapKit

@State private var cameraPosition: MapCameraPosition

init(listing: Listing) {
    // ..
        
    let region = MKCoordinateRegion(
        center: listing.city == "San Jose" ? .sanjose : .nugegoda,
        span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
    self._cameraPosition = State(initialValue: .region(region))
}

Map(position: $cameraPosition)
    .frame(height: 200)
    .clipShape(RoundedRectangle(cornerRadius: 12))

.overlay

ScrollView {
    \\ ...
}
.overlay(alignment: .bottom) {
		ReserveBar(listing: listing)
}

.tapGesture

VStack {
    \\ ...
}
.onTapGesture {
		withAnimation(.easeIn) { selectedOption = .guests }
}

.modifier

VStack {
		\\ ...
}
.modifier(CollapsibleDestinationViewModifier())

struct CollapsibleDestinationViewModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(.white)
            .clipShape(RoundedRectangle(cornerRadius: 12))
            .padding()
            .shadow(radius: 10)
    }
}

Group

Group {
    VStack {
    }
		
    VStack {
    }
    // ...
}

View๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์„ ์‹œ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋Š”๋ฐ Group์„ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค.

๐Ÿ“– Knowledge

Identifiable, Codable, Hashable

struct Listing: Identifiable, Codable, Hashable {
    let id: String
    let ownerUid: String
    let ownerName: String
    let ownerImageUrl: String
		// ...
    var features: [ListingFeatures]
    var amenities: [ListingAmenities]
    let type: ListingType
}

enum ListingFeatures: Int, Codable, Identifiable, Hashable {
    case selfCheckIn
    case superHost
    
    var title: String {
        switch self {
        case .selfCheckIn: return "Self check-in"
        case .superHost: return "Superhost"
        }
    }
    
    var subtitle: String {
        switch self {
        case .selfCheckIn: return "Check yourself in with the keypad"
        case .superHost: return "Superhosts are experienced, highly rated hosts who are commited to providing greate stars for guests."
        }
    }
    
    var imageName: String {
        switch self {
        case .selfCheckIn: return "door.left.hand.open"
        case .superHost: return "medal"
        }
    }
    
    var id: Int { return self.rawValue }
}
  • Identifiable : list๋‚˜ collection์—์„œ ๊ฐ ํ•ญ๋ชฉ์„ ๊ตฌ๋ณ„ํ•˜๋Š” id๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•œ๋‹ค.

  • Codable : ์„œ๋ฒ„์™€์˜ ๋ฐ์ดํ„ฐ ๊ตํ™˜์—์„œ JSON ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ์‚ฌ์šฉํ•œ๋‹ค.

  • Hashable : Set์ด๋‚˜ Dictionary์— ์‚ฌ์šฉํ•œ๋‹ค.

MVVM

Service

import Foundation

class ExploreService {
    func fetchListings() async throws -> [Listing] {
        return DeveloperPreview.shared.listing
    }
}
  • ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ์™€ ์ƒํ˜ธ ์ž‘์šฉ์„ ๋‹ด๋‹น
  • ๋ฐ์ดํ„ฐ ๋กœ์ง์„ ๋ทฐ๋‚˜ ๋ทฐ๋ชจ๋ธ์—์„œ ์ง์ ‘ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ณ  ์™ธ๋ถ€ ์„œ๋น„์Šค๋กœ ๋ถ„๋ฆฌํ•จ์œผ๋กœ์„œ ์œ ์ง€๋ณด์ˆ˜๋‚˜ ํ…Œ์ŠคํŠธ๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ํ•œ๋‹ค.

View

import SwiftUI

struct ExploreView: View {
		// ...
    @StateObject var viewModel = ExploreViewModel(service: ExploreService())
    
    var body: some View {
        // ...
        LazyVStack(spacing: 32) {
            ForEach(viewModel.listings) { listing in
                NavigationLink(value: listing) {
                    ListingItemView(listing: listing)
                        .frame(height: 400)
                        .clipShape(RoundedRectangle(cornerRadius: 10))
                }
            }
        }
        // ...
    }
}
  • ์œ ์ €์—๊ฒŒ ๋ณด์—ฌ์ง€๋Š” ํ™”๋ฉด์„ ์ •์˜, ์œ ์ € ์ž…๋ ฅ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ๋ทฐ์— ํ‘œ์‹œํ•  ๋ฐ์ดํ„ฐ๋ฅผ ๋ทฐ๋ชจ๋ธ์—์„œ ๊ฐ€์ ธ์™€ ๋ Œ๋”๋งํ•œ๋‹ค.

ViewModel

import Foundation

class ExploreViewModel: ObservableObject {
    @Published var listings: [Listing] = []
		@Published var searchLocation = ""
    private let service: ExploreService
    
    init(service: ExploreService) {
        self.service = service
        
        Task { await fetchListings() }
    }
    
    func fetchListings() async {
        do {
            self.listings = try await service.fetchListings()
        } catch {
            print("DEBUG: Failed to fetch listng with error: \(error.localizedDescription)")
        }
    }

		func updateListingForLocation() {
        let filteredListings = listings.filter( {
            $0.city.lowercased() == searchLocation.lowercased() ||
            $0.state.lowercased() == searchLocation.lowercased()
        })
        
        self.listings = filteredListings.isEmpty ? listingsCopy : filteredListings
    }
}
  • ๋ทฐ์— ํ‘œ์‹œํ•  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณต, ๋ทฐ์™€ ์„œ๋น„์Šค๊ฐ„์˜ ์ค‘๊ฐ„๊ณ„์ธต ์—ญํ• ์„ ํ•œ๋‹ค.

Dependency Injection

์–ด๋–ค ๊ฐ์ฒด๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ๋‹ค๋ฅธ ๊ฐ์ฒด๋ฅผ ์™ธ๋ถ€์—์„œ ์ „๋‹ฌ๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ

// ViewModel
class ExploreViewModel: ObservableObject {
	@Published var listings: [Listing] = []
	@Published var searchLocation = ""
}
// View
@StateObject var viewModel = ExploreViewModel(service: ExploreService())
  • SwiftUI์—์„œ์˜ ์˜์กด์„ฑ ์ฃผ์ž…์€ ObervableObject ๋ฅผ @ObservedObject ๋˜๋Š” @StateObject ์™€ ์กฐํ•ฉํ•˜์—ฌ ๊ตฌํ˜„ํ•œ๋‹ค.
  • ์ƒํƒœ ๋ณ€๊ฒฝ์ด ์žˆ์„ ๋•Œ @ObservedObject ๋Š” ๋ทฐ๋ฅผ ๋‹ค์‹œ ์ƒ์„ฑํ•ด์„œ ๊ทธ๋ฆฌ์ง€๋งŒ @StateObject ๋Š” ๋ทฐ๋ฅผ ๋‹ค์‹œ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ  ๋™์ผํ•œ ๋ทฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํšจ์œจ์„ฑ์ด ์ข‹๋‹ค.
  • ๊ธฐ๋ณธ์ ์œผ๋กœ @StateObject ๋ฅผ ์‚ฌ์šฉํ•˜๋˜ ํ•ด๋‹น ํ”„๋กœํผํ‹ฐ๋ฅผ subview์—์„ธ๋„ ์ฃผ์ž…์‹œ์ผœ์•ผ ํ•œ๋‹ค๋ฉด @ObservedObject ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

async / await

ํƒ„์ƒ ๋ฐฐ๊ฒฝ

๊ธฐ์กด์˜ DispatchQueue๋‚˜ completionHandler๋ฅผ ์ด์šฉํ•˜๋ฉด

  • ํด๋กœ์ € ์•ˆ์— ํด๋กœ์ € ์ด๋Ÿฐ์‹์œผ๋กœ ์ฝ”๋“œ๊ฐ€ ์•ˆ ์ข‹๊ฒŒ ๋ณด์ผ ์ˆ˜ ์žˆ๋‹ค.
  • ์—๋Ÿฌ ํ•ธ๋“ค๋ง์ด ๋ณต์žกํ•˜๊ณ  ์กฐ๊ฑด๋ฌธ์—์„œ ์ฒ˜๋ฆฌ๊ฐ€ ํž˜๋“ค๋‹ค.

์‚ฌ์šฉ ๋ฐฉ๋ฒ•

// Service
func fetchListings() async throws -> [Listing] {
    return DeveloperPreview.shared.listing
}
  • async : ๋น„๋™๊ธฐ์  ์‹คํ–‰์„ ํ•  ์ˆ˜ ์žˆ์Œ (๋ชจ๋“  ๋ช…๋ น์ด ๊ทธ๋ ‡์ง„ ์•Š์Œ)
  • throws : ์—๋Ÿฌ๋ฅผ ๋˜์งˆ ์ˆ˜ ์žˆ์Œ
// ViewModel
init(service: ExploreService) {
    self.service = service
        
    Task { await fetchListings() }
}

func fetchListings() async {
    do {
        self.listings = try await service.fetchListings()
        self.listingsCopy = listings
    } catch {
        print("DEBUG: Failed to fetch listng with error: \(error.localizedDescription)")
    }
}
  • await : ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ž‘์—…ํ•˜๋Š” ๊ณณ์ด๋ผ๊ณ  ๋ช…์‹œ์ ์œผ๋กœ ์•Œ๋ ค์คŒ (์ด ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ž‘๋™ํ•จ)

airbnb-clone's People

Contributors

yh97yhyh avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.