From 477dabf4b7713c7ed46db9cb025a40bc1c097b1b Mon Sep 17 00:00:00 2001 From: codenameVien Date: Sun, 22 Jun 2025 22:56:26 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat=20:=20stickyheader=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../VienStarbucks/Shop/OffsetModifier.swift | 30 +++ .../VienStarbucks/Shop/Views/ShopView.swift | 205 ++++++++++++++++-- .../VienStarbucks/Shop/getScreenSize.swift | 10 + 3 files changed, 223 insertions(+), 22 deletions(-) create mode 100644 vien/VienStarbucks/VienStarbucks/Shop/OffsetModifier.swift create mode 100644 vien/VienStarbucks/VienStarbucks/Shop/getScreenSize.swift diff --git a/vien/VienStarbucks/VienStarbucks/Shop/OffsetModifier.swift b/vien/VienStarbucks/VienStarbucks/Shop/OffsetModifier.swift new file mode 100644 index 0000000..2458b16 --- /dev/null +++ b/vien/VienStarbucks/VienStarbucks/Shop/OffsetModifier.swift @@ -0,0 +1,30 @@ +import SwiftUI + +struct OffsetModifier: ViewModifier { + @Binding var offset: CGFloat + var returnromStart: Bool = true + @State private var startValue: CGFloat = 0 + + func body(content: Content) -> some View { + content + .overlay( + GeometryReader { proxy in + Color.clear + .preference(key: OffsetKey.self, value: proxy.frame(in: .named("SCROLL")).minY) + .onPreferenceChange(OffsetKey.self) { value in + if startValue == 0 { + startValue = value + } + offset = value - (returnromStart ? startValue : 0) + } + } + ) + } +} + +struct OffsetKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = nextValue() + } +} diff --git a/vien/VienStarbucks/VienStarbucks/Shop/Views/ShopView.swift b/vien/VienStarbucks/VienStarbucks/Shop/Views/ShopView.swift index 86ea9c7..b5eba4d 100644 --- a/vien/VienStarbucks/VienStarbucks/Shop/Views/ShopView.swift +++ b/vien/VienStarbucks/VienStarbucks/Shop/Views/ShopView.swift @@ -1,28 +1,189 @@ +//import SwiftUI +// +//struct ShopView: View { +// @StateObject private var viewModel = ShopViewModel() +// +// var body: some View { +// ScrollView { +// VStack(alignment: .leading, spacing: 20) { +// BannerScrollView(banners: viewModel.banners) +// +// Text("All Products") +// .font(.title2).bold() +// AllProductsView(products: viewModel.allProducts) +// +// Text("Best Items") +// .font(.title2).bold() +// BestItemsPagerView(pagedItems: viewModel.bestItemsPages) +// +// Text("New Products") +// .font(.title2).bold() +// NewProductsGridView(products: viewModel.newProducts) +// } +// .padding(.horizontal, 19) +// } +// .navigationTitle("Starbucks Online Store") +// } +//} +// +// +// +//struct BannerScrollView: View { +// let banners: [Banner] +// +// var body: some View { +// ScrollView(.horizontal, showsIndicators: false) { +// LazyHStack(spacing:28) { +// ForEach(banners) { banner in +// Image(banner.imageName) +// .resizable() +// .scaledToFill() +// .frame(width: 270, height: 216) +// } +// } +// .padding(.horizontal) +// } +// } +//} +// +// +//struct AllProductsView: View { +// let products: [Product] +// +// var body: some View { +// ScrollView(.horizontal) { +// LazyHStack(spacing: 17) { +// ForEach(products) { product in +// VStack { +// Image(product.image) +// .resizable() +// .frame(width: 80, height: 80) +// Text(product.name) +// .font(.caption) +// } +// } +// } +// .padding(.horizontal) +// } +// } +//} +// +// +//struct BestItemsPagerView: View { +// var pagedItems: [[Product]] +// @State private var currentPage = 0 +// +// var body: some View { +// TabView(selection: $currentPage) { +// ForEach(0.. some View { + GeometryReader { proxy in + let minY = proxy.frame(in: .named("SCROLL")).minY + let height = max(0, proxy.size.height + minY) + + Color.white + .frame(height: height) + .offset(y: -minY) + } + .frame(height: 20) + } + + private func stickyHeaderView() -> some View { + let threshold = -(getScreenSize().height * 0.05) + + return HStack { + if headerOffsets.0 < threshold { + Spacer() } - .padding(.horizontal, 19) + + Text("Starbucks Online Store") + .font(headerOffsets.0 < threshold ? .title : .title2) + .fontWeight(.bold) + .animation(.easeInOut(duration: 0.3), value: headerOffsets.0) + + Spacer() } - .navigationTitle("Starbucks Online Store") + .frame(height: 90) + .padding(.bottom, headerOffsets.0 < threshold ? 10 : 0) + .background(Color.white) + .modifier(OffsetModifier(offset: $headerOffsets.0, returnromStart: false)) + .modifier(OffsetModifier(offset: $headerOffsets.1)) + .zIndex(10) } } @@ -30,7 +191,7 @@ struct ShopView: View { struct BannerScrollView: View { let banners: [Banner] - + var body: some View { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing:28) { @@ -49,7 +210,7 @@ struct BannerScrollView: View { struct AllProductsView: View { let products: [Product] - + var body: some View { ScrollView(.horizontal) { LazyHStack(spacing: 17) { @@ -72,7 +233,7 @@ struct AllProductsView: View { struct BestItemsPagerView: View { var pagedItems: [[Product]] @State private var currentPage = 0 - + var body: some View { TabView(selection: $currentPage) { ForEach(0.. CGSize { + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { + return .zero + } + return windowScene.screen.bounds.size + } +} From 99b13ed13eca72f8e485f6f6f3c0a590bbaa6b2d Mon Sep 17 00:00:00 2001 From: codenameVien Date: Mon, 23 Jun 2025 00:13:24 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat=20:=20carousel=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../VienStarbucks.xcodeproj/project.pbxproj | 2 + .../Pay/CustomPagingSlider.swift | 68 +++++++++++++++++++ .../VienStarbucks/Pay/PayModel.swift | 10 +++ .../VienStarbucks/Pay/PayView.swift | 48 +++++++++++++ .../VienStarbucks/Pay/PayViewModel.swift | 8 +++ .../VienStarbucks/Views/MainTabView.swift | 6 -- 6 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 vien/VienStarbucks/VienStarbucks/Pay/CustomPagingSlider.swift create mode 100644 vien/VienStarbucks/VienStarbucks/Pay/PayModel.swift create mode 100644 vien/VienStarbucks/VienStarbucks/Pay/PayView.swift create mode 100644 vien/VienStarbucks/VienStarbucks/Pay/PayViewModel.swift diff --git a/vien/VienStarbucks/VienStarbucks.xcodeproj/project.pbxproj b/vien/VienStarbucks/VienStarbucks.xcodeproj/project.pbxproj index b16ecd6..cfa7d0c 100644 --- a/vien/VienStarbucks/VienStarbucks.xcodeproj/project.pbxproj +++ b/vien/VienStarbucks/VienStarbucks.xcodeproj/project.pbxproj @@ -329,6 +329,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"VienStarbucks/Preview Content\""; + DEVELOPMENT_TEAM = VRLZVZ52F5; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VienStarbucks/Info.plist; @@ -358,6 +359,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"VienStarbucks/Preview Content\""; + DEVELOPMENT_TEAM = VRLZVZ52F5; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VienStarbucks/Info.plist; diff --git a/vien/VienStarbucks/VienStarbucks/Pay/CustomPagingSlider.swift b/vien/VienStarbucks/VienStarbucks/Pay/CustomPagingSlider.swift new file mode 100644 index 0000000..76fa329 --- /dev/null +++ b/vien/VienStarbucks/VienStarbucks/Pay/CustomPagingSlider.swift @@ -0,0 +1,68 @@ +// CustomPagingSlider.swift +// CustomSlider +// +// Created by 이은정 on 5/31/25. +// + +import SwiftUI + +//Paging Slider Data Model +struct Item: Identifiable { + private(set) var id: UUID = .init() + var color: Color + var title: String + var subtitle: String +} + + +//Content View +struct CustomPagingSlider: View where Item: MutableCollection, Item.Element: Identifiable { + // Customization Properties + var showsIndicators: ScrollIndicatorVisibility = .hidden + var showPagingControl: Bool = true + var titleScrollSpeed : CGFloat = 0.6 + var pagingControlSpacing: CGFloat = 20 + var spacing: CGFloat = 10 + + @Binding var data: Item + @ViewBuilder var content: (Binding) -> Content + @ViewBuilder var titleContent: (Binding) -> TitleContent + var body: some View { + + VStack(spacing: pagingControlSpacing){ + ScrollView(.horizontal){ + HStack(spacing: spacing) { + ForEach($data) { item in + VStack(spacing: 0) { + titleContent(item) +// .frame(maxWidth: .infinity) +// .visualEffect { content, geometryProxy in content +// .offset(x: scrollOffset(geometryProxy)) +// } + + content(item) + } + .containerRelativeFrame(.horizontal) + } + } + + } + ///Adding Paging + .scrollTargetLayout() + } + .scrollIndicators(showsIndicators) + .scrollTargetBehavior(.viewAligned) + } + + +// func scrollOffset(_ proxy: GeometryProxy) -> CGFloat { +// let minX = proxy.bounds(of: .scrollView)?.minX ?? 0 +// +// return -minX * min(titleScrollSpeed, 1.0) +// } +} + + +#Preview { + ContentView() +} diff --git a/vien/VienStarbucks/VienStarbucks/Pay/PayModel.swift b/vien/VienStarbucks/VienStarbucks/Pay/PayModel.swift new file mode 100644 index 0000000..a9645ae --- /dev/null +++ b/vien/VienStarbucks/VienStarbucks/Pay/PayModel.swift @@ -0,0 +1,10 @@ +import SwiftUI + +struct PayModel: Identifiable { + let id = UUID() + var cardName: String + var balance: Int + var cardNumber: String + var expireTime: String + var imageName: String +} diff --git a/vien/VienStarbucks/VienStarbucks/Pay/PayView.swift b/vien/VienStarbucks/VienStarbucks/Pay/PayView.swift new file mode 100644 index 0000000..f5d49b8 --- /dev/null +++ b/vien/VienStarbucks/VienStarbucks/Pay/PayView.swift @@ -0,0 +1,48 @@ +import SwiftUI + +struct PayView: View { + @StateObject private var viewModel = PayViewModel() + + var body: some View { + VStack(alignment: .leading, spacing: 20) { + Text("Pay") + .font(.largeTitle.bold()) + .padding(.leading, 20) + + CustomPagingSlider(data: $viewModel.cards) { $item in + Image(item.imageName) + .resizable() + .scaledToFit() + .frame(width: 250, height: 150) + .cornerRadius(10) + } titleContent: { $item in + VStack(spacing: 8) { + Text(item.cardName) + .font(.subheadline) + .foregroundColor(.gray) + + Text("\(item.balance)원") + .font(.title2.bold()) + + Text(item.cardNumber) + .font(.caption) + .foregroundColor(.gray) + + Text("카드 유효 시간 \(item.expireTime)") + .font(.caption2) + .foregroundColor(.green) + } + .frame(maxWidth: .infinity) + } + .frame(height: 260) + .padding(.horizontal) + + Spacer() + } + .padding(.top) + } +} + +#Preview { + PayView() +} diff --git a/vien/VienStarbucks/VienStarbucks/Pay/PayViewModel.swift b/vien/VienStarbucks/VienStarbucks/Pay/PayViewModel.swift new file mode 100644 index 0000000..e9a97ca --- /dev/null +++ b/vien/VienStarbucks/VienStarbucks/Pay/PayViewModel.swift @@ -0,0 +1,8 @@ +import SwiftUI + +class PayViewModel: ObservableObject { + @Published var cards: [PayModel] = [ + PayModel(cardName: "아무카드 A", balance: 999, cardNumber: "****-****-1234", expireTime: "03:00", imageName: "pay/StarbucksCard"), + PayModel(cardName: "아무카드 B", balance: 10000, cardNumber: "****-****-5678", expireTime: "04:00", imageName: "pay/StarbucksCard") + ] +} diff --git a/vien/VienStarbucks/VienStarbucks/Views/MainTabView.swift b/vien/VienStarbucks/VienStarbucks/Views/MainTabView.swift index 6cafe4f..68b366b 100644 --- a/vien/VienStarbucks/VienStarbucks/Views/MainTabView.swift +++ b/vien/VienStarbucks/VienStarbucks/Views/MainTabView.swift @@ -30,12 +30,6 @@ struct MainTabView: View { } -struct PayView: View { - var body: some View { - Text("Pay") - } -} - struct OrderView: View { var body: some View { Text("Order")