From d820d57d2fcb76860267f9efa2b0210c1e8b966e Mon Sep 17 00:00:00 2001 From: Roy Date: Mon, 11 Aug 2025 16:42:16 +0900 Subject: [PATCH 01/20] =?UTF-8?q?=E2=9C=A8[feat]:=20=EA=B3=A7=ED=86=B5=20?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=93=B8=20=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=A0=84=ED=99=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 공통으로 사용할 화면 전환 로직 코디네이터 구현 * navigationStack 방식으로 구현 --- .../Application/TeamIntroduceApp.swift | 16 +---- .../Navigation/NavigationControlling.swift | 69 +++++++++++++++++++ .../Flow/IntroduceCoordinator.swift | 58 ++++++++++++++++ .../Coordinator/Flow/IntroduceRoute.swift | 45 ++++++++++++ .../View/IntorduceCoordinatorView.swift | 50 ++++++++++++++ .../IntroduceMain/View/ContentView.swift | 62 +++++++---------- .../Sources/Presnetaion/Root/RootView.swift | 20 ++++++ 7 files changed, 266 insertions(+), 54 deletions(-) create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Common/Navigation/NavigationControlling.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift diff --git a/TeamIntroduce/TeamIntroduce/Sources/Application/TeamIntroduceApp.swift b/TeamIntroduce/TeamIntroduce/Sources/Application/TeamIntroduceApp.swift index 3cabac3..0adc934 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Application/TeamIntroduceApp.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Application/TeamIntroduceApp.swift @@ -10,23 +10,9 @@ import SwiftData @main struct TeamIntroduceApp: App { - var sharedModelContainer: ModelContainer = { - let schema = Schema([ - Item.self, - ]) - let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) - - do { - return try ModelContainer(for: schema, configurations: [modelConfiguration]) - } catch { - fatalError("Could not create ModelContainer: \(error)") - } - }() - var body: some Scene { WindowGroup { - ContentView() + RootView() } - .modelContainer(sharedModelContainer) } } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Common/Navigation/NavigationControlling.swift b/TeamIntroduce/TeamIntroduce/Sources/Common/Navigation/NavigationControlling.swift new file mode 100644 index 0000000..f430184 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Common/Navigation/NavigationControlling.swift @@ -0,0 +1,69 @@ +// +// NavigationControlling.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/11/25. +// + +import Foundation + +import Foundation +import SwiftUI + +// MARK: - Navigation 제어 프로토콜 + +/// SwiftUI `NavigationStack` 기반 화면 전환을 제어하는 공통 프로토콜입니다. +/// +/// 각 탭 또는 플로우 별로 코디네이터가 이 프로토콜을 채택하면 +/// 공통적인 `goBack`, `reset` 동작을 재사용할 수 있습니다. +protocol NavigationControlling: AnyObject { + + /// 현재 네비게이션 경로 상태입니다. + /// + /// `NavigationStack(path:)`에 바인딩되어 화면 이동을 제어합니다. + var path: NavigationPath { get set } + + /// 초기 진입 지점을 설정하는 메서드입니다. + /// + /// 각 코디네이터에서 구현되어야 하며, + /// 앱 시작 또는 탭 변경 시 루트 화면을 정의합니다. + func start() + + /// 마지막 화면을 스택에서 제거하여 이전 화면으로 이동합니다. + func goBack() + + /// 스택의 모든 경로를 제거하고 루트 화면으로 돌아갑니다. + func reset() +} + +// MARK: - NavigationControlling 기본 구현 + +extension NavigationControlling { + + /// 현재 화면을 스택에서 팝(뒤로 가기)합니다. + /// + /// - 동작: + /// - `path`가 비어있지 않은 경우, 마지막 항목을 제거하여 이전 화면으로 이동합니다. + /// + /// - 예시: + /// ```swift + /// coordinator.goBack() + /// ``` + func goBack() { + guard !path.isEmpty else { return } + path.removeLast() + } + + /// 네비게이션 스택을 초기 상태(루트)로 리셋합니다. + /// + /// - 동작: + /// - 현재 경로 배열에서 모든 화면을 제거하여, 루트 화면만 유지합니다. + /// + /// - 예시: + /// ```swift + /// coordinator.reset() + /// ``` + func reset() { + path.removeLast(path.count) + } +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift new file mode 100644 index 0000000..596b935 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift @@ -0,0 +1,58 @@ +// +// IntroduceCoordinator.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/11/25. +// + +import Combine +import SwiftUI + +import SwiftUI +import Combine + +final class IntroduceCoordinator: NavigationControlling, ObservableObject { + + @Published var path = NavigationPath() + + // 액션 기반 네비게이션 + enum Action { + case start + case presentMain + case pop + case popToRoot + case presntDetail + } + + func send(_ action: Action) { + switch action { + case .start: + start() + + case .presentMain: + path.append(IntroduceRoute(route: .introduceMain)) + + case .pop: + if !path.isEmpty { path.removeLast() } + + case .popToRoot: + path = .init() + + case .presntDetail: + path.append(IntroduceRoute(route: .teamAgreement)) + + } + } + + // MARK: - NavigationControlling 요구 구현 + + func start() { + reset() + send(.presentMain) + } + + // ⚠️ 프로토콜이 요구한다면 private 붙이면 안 됨 + func reset() { + path = .init() + } +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift new file mode 100644 index 0000000..8bc635b --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift @@ -0,0 +1,45 @@ +// +// IntroduceRoute.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/11/25. +// + +enum IntroduceRoute: Hashable { + + // 소개 페이지 메인 화면 + case introduceMain + // 팀약속 + case teamAgreement + // 팀소개 + case teamIntroduce + // 팀 블로그 + case temBlog + + + // MARK: - 내부 전용 초기화 + + /// 내부 Route 값을 기반으로 IntroduceRoute를 생성합니다. + /// + /// 외부에서는 직접 case를 생성하지 않고, 내부에서만 변환을 허용합니다. + /// + /// - Parameter route: 내부용 Route enum 값 + init(route: Route) { + switch route { + case .introduceMain: self = .introduceMain + case .teamAgreement : self = .teamAgreement + case .teamIntroduce: self = .teamIntroduce + case .temBlog: self = .temBlog + } + } + + // MARK: - 내부 전용 라우트 Enum + + /// 외부 접근은 가능하지만 직접 IntroduceRoute를 생성할 수 없도록 제어하기 위한 내부 enum입니다. + enum Route { + case introduceMain + case teamAgreement + case teamIntroduce + case temBlog + } +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift new file mode 100644 index 0000000..4fd9797 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift @@ -0,0 +1,50 @@ +// +// IntorduceCoordinatorView.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/11/25. +// + +import SwiftUI +import SwiftData + +struct IntorduceCoordinatorView : View { + @EnvironmentObject private var coordinator: IntroduceCoordinator + var sharedModelContainer: ModelContainer = { + let schema = Schema([ + Item.self, + ]) + let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) + + do { + return try ModelContainer(for: schema, configurations: [modelConfiguration]) + } catch { + fatalError("Could not create ModelContainer: \(error)") + } + }() + + var body: some View { + NavigationStack(path: $coordinator.path) { + ContentView() + .navigationDestination(for: IntroduceRoute.self, destination: makeDestination) + } + .modelContainer(sharedModelContainer) + } +} + + +extension IntorduceCoordinatorView { + @ViewBuilder + private func makeDestination(for route: IntroduceRoute) -> some View { + switch route { + case .introduceMain: + ContentView() + case .teamAgreement: + ContentView() + case .teamIntroduce: + EmptyView() + case .temBlog: + EmptyView() + } + } +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift index 1770e51..7d5a2a6 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift @@ -9,53 +9,37 @@ import SwiftUI import SwiftData struct ContentView: View { - @Environment(\.modelContext) private var modelContext - @Query private var items: [Item] + @Environment(\.modelContext) private var modelContext + @Query private var items: [Item] + @EnvironmentObject private var coordinator: IntroduceCoordinator - var body: some View { - NavigationSplitView { - List { - ForEach(items) { item in - NavigationLink { - Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") - } label: { - Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) - } - } - .onDelete(perform: deleteItems) - } - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - EditButton() - } - ToolbarItem { - Button(action: addItem) { - Label("Add Item", systemImage: "plus") - } - } - } - } detail: { - Text("Select an item") + + var body: some View { + VStack { + Text("main") + .onTapGesture { + coordinator.send(.presntDetail) } } + } - private func addItem() { - withAnimation { - let newItem = Item(timestamp: Date()) - modelContext.insert(newItem) - } + private func addItem() { + withAnimation { + let newItem = Item(timestamp: Date()) + modelContext.insert(newItem) } + } - private func deleteItems(offsets: IndexSet) { - withAnimation { - for index in offsets { - modelContext.delete(items[index]) - } - } + private func deleteItems(offsets: IndexSet) { + withAnimation { + for index in offsets { + modelContext.delete(items[index]) + } } + } } #Preview { - ContentView() - .modelContainer(for: Item.self, inMemory: true) + ContentView() + .modelContainer(for: Item.self, inMemory: true) } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift new file mode 100644 index 0000000..f1ce0c8 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift @@ -0,0 +1,20 @@ +// +// RootView.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/11/25. +// + +import SwiftUI + +struct RootView: View { + @StateObject var coordinator = IntroduceCoordinator() + var body: some View { + IntorduceCoordinatorView() + .environmentObject(coordinator) + } +} + +#Preview { + RootView() +} From f5349260d6922efc03aab3ee0f6df2a01ebb91be Mon Sep 17 00:00:00 2001 From: Roy Date: Mon, 11 Aug 2025 17:38:39 +0900 Subject: [PATCH 02/20] =?UTF-8?q?=E2=9C=A8[feat]:=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EA=B4=80=EB=A0=A8=20=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assets.xcassets/icon/Contents.json | 6 ++++++ .../icon/people.imageset/Contents.json | 21 +++++++++++++++++++ .../icon/people.imageset/people.svg | 6 ++++++ .../icon/rightArrow.imageset/Contents.json | 21 +++++++++++++++++++ .../icon/rightArrow.imageset/rightArrow.svg | 3 +++ .../Componet/ListRowComponet.swift | 18 ++++++++++++++++ .../DesignSytstem/Image/Extension+Image.swift | 0 .../DesignSytstem/Image/ImageAsset.swift | 0 8 files changed, 75 insertions(+) create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/people.svg create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/rightArrow.svg create mode 100644 TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/Extension+Image.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/Contents.json new file mode 100644 index 0000000..1cc8d72 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "people.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/people.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/people.svg new file mode 100644 index 0000000..3b74325 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/people.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/Contents.json new file mode 100644 index 0000000..bfef48d --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "rightArrow.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/rightArrow.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/rightArrow.svg new file mode 100644 index 0000000..894dae3 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/rightArrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift new file mode 100644 index 0000000..a373e61 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift @@ -0,0 +1,18 @@ +// +// ListRowComponet.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/11/25. +// + +import SwiftUI + +struct ListRowComponet: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + ListRowComponet() +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/Extension+Image.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/Extension+Image.swift new file mode 100644 index 0000000..e69de29 diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift new file mode 100644 index 0000000..e69de29 From 26e8844b0415ff5a9d29834bffce7d324cfeee41 Mon Sep 17 00:00:00 2001 From: Roy Date: Mon, 11 Aug 2025 17:39:14 +0900 Subject: [PATCH 03/20] =?UTF-8?q?=E2=9C=A8[feat]:=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EA=B4=80=EB=A0=A8=20=20=EC=B6=94=EA=B0=80=EB=B0=8F?= =?UTF-8?q?=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=B7=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Navigation/NavigationControlling.swift | 2 - .../Color/Extension+ShapeStyle.swift | 7 +- .../Componet/ListRowComponet.swift | 74 ++++++++++++++++++- .../DesignSytstem/Image/Extension+Image.swift | 36 +++++++++ .../DesignSytstem/Image/ImageAsset.swift | 13 ++++ .../Flow/IntroduceCoordinator.swift | 1 - .../IntroduceMain/View/ContentView.swift | 36 +++++++-- 7 files changed, 156 insertions(+), 13 deletions(-) diff --git a/TeamIntroduce/TeamIntroduce/Sources/Common/Navigation/NavigationControlling.swift b/TeamIntroduce/TeamIntroduce/Sources/Common/Navigation/NavigationControlling.swift index f430184..fe5dcb2 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Common/Navigation/NavigationControlling.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Common/Navigation/NavigationControlling.swift @@ -5,8 +5,6 @@ // Created by Wonji Suh on 8/11/25. // -import Foundation - import Foundation import SwiftUI diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift index 5841e07..49e14e1 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift @@ -13,12 +13,13 @@ extension ShapeStyle where Self == Color { static var staticWhite: Color { .init(hex: "FFFFFF") } static var staticBlack: Color { .init(hex: "0C0E0F") } + static var shadowColor: Color { .init(hex: "000000")} // MARK: - Static Text static var textPrimary: Color { .init(hex: "FFFFFF") } - static var textSecondary: Color { .init(hex: "EAEAEA") } - static var textSecondary100: Color { .init(hex: "525252") } + static var textSecondary: Color { .init(hex: "717182") } + static var textSecondary100: Color { .init(hex: "171725") } static var textInactive: Color { .init(hex: "70737C47").opacity(0.28) } // MARK: - Static Background @@ -68,7 +69,7 @@ extension ShapeStyle where Self == Color { // MARK: - NatureBlue - static var blue10: Color { .init(hex: "F5F8FF") } + static var blue10: Color { .init(hex: "155DFC") } static var blue20: Color { .init(hex: "E1EAFF") } static var blue30: Color { .init(hex: "C1D3FF") } static var blue40: Color { .init(hex: "0D82F9") } diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift index a373e61..85fcca4 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift @@ -8,11 +8,81 @@ import SwiftUI struct ListRowComponet: View { + var color: Color + var image: ImageAsset + var title: String + var subContent: String + var blogUrl: String + var showArrow: Bool + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + VStack { + HStack { + Circle() + .fill(.white) + .frame(width: 35, height: 35) + .overlay { + Image(asset: .people) + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + } + + VStack(alignment: .leading, spacing: .zero) { + Text("팀소개") + .pretendardFont(family: .SemiBold, size: 14) + .foregroundStyle(.basicBlack) + + Spacer() + .frame(height: 4) + + Text("우리 팀의 특징과 목표") + .pretendardFont(family: .Regular, size: 12) + .foregroundStyle(.textSecondary) + + if !blogUrl.isEmpty { + Text(blogUrl) + .pretendardFont(family: .Regular, size: 10) + .foregroundStyle(.textSecondary100) + } + + + } + + Spacer() + + if showArrow { + Image(asset: .rightArrow) + .resizable() + .scaledToFit() + .frame(width: 12, height: 12) + .onTapGesture { + + } + } + + + } + } + .padding(.vertical, 16) + .padding(.horizontal, 15) + .frame(height: 69) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.staticWhite) + .shadow( color: .shadowColor, radius: 1) + ) + } } #Preview { - ListRowComponet() + ListRowComponet( + color: .blue10, + image: .people, + title: "팀소개", + subContent: "우리 팀의 특징과 목표", + blogUrl: "", + showArrow: true + ) } diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/Extension+Image.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/Extension+Image.swift index e69de29..229a230 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/Extension+Image.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/Extension+Image.swift @@ -0,0 +1,36 @@ +// +// Extension+Image.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/11/25. +// + +import SwiftUI + + extension UIImage { + convenience init?(_ asset: ImageAsset) { + self.init(named: asset.rawValue, in: Bundle.main, with: nil) + } + + convenience init?(assetName: String) { + self.init(named: assetName, in: Bundle.main, with: nil) + } +} + +extension Image { + init(asset: ImageAsset) { + if let uiImage = UIImage(asset) { + self.init(uiImage: uiImage) + } else { + self = Image(systemName: "questionmark") + } + } + + init(assetName: String) { + if let uiImage = UIImage(assetName: assetName) { + self.init(uiImage: uiImage) + } else { + self = Image(systemName: "questionmark") + } + } +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift index e69de29..1cf2dfc 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift @@ -0,0 +1,13 @@ +// +// ImageAsset.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/11/25. +// + +import Foundation + +enum ImageAsset: String { + case people + case rightArrow +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift index 596b935..cdecbb6 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift @@ -45,7 +45,6 @@ final class IntroduceCoordinator: NavigationControlling, ObservableObject { } // MARK: - NavigationControlling 요구 구현 - func start() { reset() send(.presentMain) diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift index 7d5a2a6..609f6e0 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift @@ -15,11 +15,23 @@ struct ContentView: View { var body: some View { - VStack { - Text("main") - .onTapGesture { - coordinator.send(.presntDetail) - } + ZStack { + Color.white + .edgesIgnoringSafeArea(.all) + + VStack { + Text("main") + .onTapGesture { + coordinator.send(.presntDetail) + } + + Spacer() + + moreInfoSection() + + Spacer() + .frame(height: 20) + } } } @@ -39,6 +51,20 @@ struct ContentView: View { } } + +extension ContentView { + @ViewBuilder + private func moreInfoSection() -> some View { + VStack { + HStack { + Text("더 알아보기") + .pretendardFont(family:.SemiBold, size: 20) + + } + } + } +} + #Preview { ContentView() .modelContainer(for: Item.self, inMemory: true) From 51514ceb213c49ece4be6645c247538111986263 Mon Sep 17 00:00:00 2001 From: Roy Date: Mon, 11 Aug 2025 19:58:07 +0900 Subject: [PATCH 04/20] =?UTF-8?q?=E2=9C=A8[feat]:=20=20=EB=8D=94=20?= =?UTF-8?q?=EC=95=8C=EC=95=84=20=EB=B3=B4=EA=B8=B0=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../icon/blog.imageset/Contents.json | 21 ++++++ .../icon/blog.imageset/blog.svg | 5 ++ .../icon/check.imageset/Contents.json | 21 ++++++ .../icon/check.imageset/check.svg | 4 ++ .../Color/Extension+ShapeStyle.swift | 4 ++ .../Componet/ListRowComponet.swift | 44 +++++++++---- .../DesignSytstem/Image/ImageAsset.swift | 2 + .../IntroduceMain/View/ContentView.swift | 26 ++++++++ .../IntroduceMain/View/MoreInfoItem.swift | 66 +++++++++++++++++++ 9 files changed, 181 insertions(+), 12 deletions(-) create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/blog.svg create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/check.svg create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/MoreInfoItem.swift diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/Contents.json new file mode 100644 index 0000000..3e8c32f --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "blog.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/blog.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/blog.svg new file mode 100644 index 0000000..d3b5f50 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/blog.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/Contents.json new file mode 100644 index 0000000..17203cc --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "check.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/check.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/check.svg new file mode 100644 index 0000000..daadbb8 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift index 49e14e1..bac1789 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift @@ -100,6 +100,10 @@ extension ShapeStyle where Self == Color { static var gray600: Color { .init(hex: "808080") } static var gray800: Color { .init(hex: "4D4D4D") } + static var green: Color { .init(hex: "00A63E") } + static var lightPurple: Color { .init(hex: "9810FA") } + + static var error: Color { .init(hex: "FF5050") } static var basicBlue: Color { .init(hex: "0099FF") } diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift index 85fcca4..f23e561 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift @@ -8,35 +8,54 @@ import SwiftUI struct ListRowComponet: View { - var color: Color - var image: ImageAsset - var title: String - var subContent: String - var blogUrl: String - var showArrow: Bool + private var color: Color + private var image: ImageAsset + private var title: String + private var subContent: String + private var blogUrl: String + private var showArrow: Bool + private var arrowAction: () -> Void = {} + + init( + color: Color, + image: ImageAsset, + title: String, + subContent: String, + blogUrl: String, + showArrow: Bool, + arrowAction: @escaping () -> Void + ) { + self.color = color + self.image = image + self.title = title + self.subContent = subContent + self.blogUrl = blogUrl + self.showArrow = showArrow + self.arrowAction = arrowAction + } var body: some View { VStack { HStack { Circle() - .fill(.white) + .fill(color) .frame(width: 35, height: 35) .overlay { - Image(asset: .people) + Image(asset: image) .resizable() .scaledToFit() .frame(width: 20, height: 20) } VStack(alignment: .leading, spacing: .zero) { - Text("팀소개") + Text(title) .pretendardFont(family: .SemiBold, size: 14) .foregroundStyle(.basicBlack) Spacer() .frame(height: 4) - Text("우리 팀의 특징과 목표") + Text(subContent) .pretendardFont(family: .Regular, size: 12) .foregroundStyle(.textSecondary) @@ -57,7 +76,7 @@ struct ListRowComponet: View { .scaledToFit() .frame(width: 12, height: 12) .onTapGesture { - + arrowAction() } } @@ -83,6 +102,7 @@ struct ListRowComponet: View { title: "팀소개", subContent: "우리 팀의 특징과 목표", blogUrl: "", - showArrow: true + showArrow: true, + arrowAction: {} ) } diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift index 1cf2dfc..824360e 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift @@ -10,4 +10,6 @@ import Foundation enum ImageAsset: String { case people case rightArrow + case blog + case check } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift index 609f6e0..024173f 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift @@ -59,9 +59,35 @@ extension ContentView { HStack { Text("더 알아보기") .pretendardFont(family:.SemiBold, size: 20) + .foregroundStyle(.basicBlack) + Spacer() + + } + + Spacer() + .frame(height: 14) + + + ForEach(MoreInfoItem.moreInfoList, id: \.self) { item in + VStack { + ListRowComponet( + color: item.color, + image: item.images, + title: item.titleContent, + subContent: item.subtitleContent, + blogUrl: "", + showArrow: true, + arrowAction: { + + } + ) + .padding(.vertical, 3) + } } + } + .padding(.horizontal, 24) } } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/MoreInfoItem.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/MoreInfoItem.swift new file mode 100644 index 0000000..1e2c4ee --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/MoreInfoItem.swift @@ -0,0 +1,66 @@ +// +// MoreInfoItem.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/11/25. +// + +import Foundation +import SwiftUI + +enum MoreInfoItem: String , CaseIterable , Identifiable { + case teamIntroduce + case teamAgreement + case teamBlog + var id: String { rawValue } + + var titleContent: String { + switch self { + case .teamIntroduce: + return "팀 소개" + case .teamAgreement: + return "팀 약속" + case .teamBlog: + return "팀 블로그" + } + } + + var subtitleContent: String { + switch self { + case .teamIntroduce: + return"우리 팀의 특징과 목표" + case .teamAgreement: + return "함께 지켜나갈 소중한 약속들" + case .teamBlog: + return "팀원들의 블로그 모음" + } + } + + var images: ImageAsset { + switch self { + case .teamIntroduce: + return .people + case .teamAgreement: + return .check + case .teamBlog: + return .blog + } + } + + var color: Color { + switch self { + case .teamIntroduce: + return .blue10.opacity(0.1) + case .teamAgreement: + return .green.opacity(0.1) + case .teamBlog: + return .lightPurple.opacity(0.1) + } + } + + + static var moreInfoList: [MoreInfoItem] { + [.teamIntroduce, .teamAgreement, .teamBlog] + } + +} From 262d25e21f5faa0d398c8bf4d664db622024467a Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 09:11:56 +0900 Subject: [PATCH 05/20] =?UTF-8?q?=E2=9C=A8[feat]:=20=EA=B3=A7=ED=86=B5=20?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=93=B8=20=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Componet/ListRowComponet.swift | 110 ++++++++++-------- .../IntroduceMain/View/ContentView.swift | 13 ++- 2 files changed, 71 insertions(+), 52 deletions(-) diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift index f23e561..2ab9d7b 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift @@ -8,13 +8,20 @@ import SwiftUI struct ListRowComponet: View { + // 기존 프로퍼티 private var color: Color private var image: ImageAsset private var title: String private var subContent: String private var blogUrl: String private var showArrow: Bool - private var arrowAction: () -> Void = {} + private var moreInfoItem: MoreInfoItem + + // 선택 전달 (부모가 소유) + @Binding private var selection: MoreInfoItem + + // 선택 시 추가 작업이 필요하면 사용 (옵셔널) + private var arrowAction: ((MoreInfoItem) -> Void)? init( color: Color, @@ -23,7 +30,9 @@ struct ListRowComponet: View { subContent: String, blogUrl: String, showArrow: Bool, - arrowAction: @escaping () -> Void + moreInfoItem: MoreInfoItem, + selection: Binding, + arrowAction: ((MoreInfoItem) -> Void)? = nil ) { self.color = color self.image = image @@ -31,68 +40,71 @@ struct ListRowComponet: View { self.subContent = subContent self.blogUrl = blogUrl self.showArrow = showArrow + self.moreInfoItem = moreInfoItem + self._selection = selection self.arrowAction = arrowAction } - var body: some View { - VStack { - HStack { - Circle() - .fill(color) - .frame(width: 35, height: 35) - .overlay { - Image(asset: image) - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - } - - VStack(alignment: .leading, spacing: .zero) { - Text(title) - .pretendardFont(family: .SemiBold, size: 14) - .foregroundStyle(.basicBlack) - - Spacer() - .frame(height: 4) + var body: some View { + VStack { + HStack { + Circle() + .fill(color) + .frame(width: 35, height: 35) + .overlay { + Image(asset: image) + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + } - Text(subContent) - .pretendardFont(family: .Regular, size: 12) - .foregroundStyle(.textSecondary) + VStack(alignment: .leading, spacing: 0) { + Text(title) + .pretendardFont(family: .SemiBold, size: 14) + .foregroundStyle(.basicBlack) - if !blogUrl.isEmpty { - Text(blogUrl) - .pretendardFont(family: .Regular, size: 10) - .foregroundStyle(.textSecondary100) - } + Spacer().frame(height: 4) + Text(subContent) + .pretendardFont(family: .Regular, size: 12) + .foregroundStyle(.textSecondary) + if !blogUrl.isEmpty { + Text(blogUrl) + .pretendardFont(family: .Regular, size: 10) + .foregroundStyle(.textSecondary100) } + } - Spacer() + Spacer() - if showArrow { + if showArrow { + // 👉 버튼으로 만들어 터치영역/접근성 강화 + Button { + selection = moreInfoItem // ✅ 선택 상태 갱신 + arrowAction?(moreInfoItem) // 필요 시 추가 콜백 + } label: { Image(asset: .rightArrow) .resizable() .scaledToFit() .frame(width: 12, height: 12) - .onTapGesture { - arrowAction() - } + .padding(10) // 터치영역 확장 } - - + .buttonStyle(.plain) + .contentShape(Rectangle()) + .accessibilityLabel(Text("\(title) 열기")) } } - .padding(.vertical, 16) - .padding(.horizontal, 15) - .frame(height: 69) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(.staticWhite) - .shadow( color: .shadowColor, radius: 1) - ) - } + .padding(.vertical, 16) + .padding(.horizontal, 15) + .frame(height: 69) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.staticWhite) + .shadow(color: .shadowColor, radius: 1) + ) + } } #Preview { @@ -103,6 +115,10 @@ struct ListRowComponet: View { subContent: "우리 팀의 특징과 목표", blogUrl: "", showArrow: true, - arrowAction: {} + moreInfoItem: .teamBlog, + selection: .constant(.teamBlog), + arrowAction: { item in + + } ) } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift index 024173f..12485df 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift @@ -12,7 +12,7 @@ struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] @EnvironmentObject private var coordinator: IntroduceCoordinator - + @State private var moreinfoitem: MoreInfoItem = .teamIntroduce var body: some View { ZStack { @@ -68,7 +68,6 @@ extension ContentView { Spacer() .frame(height: 14) - ForEach(MoreInfoItem.moreInfoList, id: \.self) { item in VStack { ListRowComponet( @@ -78,17 +77,21 @@ extension ContentView { subContent: item.subtitleContent, blogUrl: "", showArrow: true, - arrowAction: { - + moreInfoItem: moreinfoitem, + selection: $moreinfoitem, + arrowAction: { item in + print("item : \(item)") } ) .padding(.vertical, 3) } } - } .padding(.horizontal, 24) } + + + } #Preview { From efd207a78cea365420506f3561979640d45a1ee8 Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 12:30:54 +0900 Subject: [PATCH 06/20] =?UTF-8?q?=E2=9C=A8[feat]:=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=20=EB=B8=94?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EB=B7=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../icon/glabal.imageset/Contents.json | 21 +++ .../icon/glabal.imageset/glabal.svg | 1 + .../icon/leftArrow.imageset/Contents.json | 21 +++ .../icon/leftArrow.imageset/leftArrow.svg | 9 ++ .../icon/link.imageset/Contents.json | 21 +++ .../icon/link.imageset/link.svg | 1 + .../Componet/ListRowComponet.swift | 124 ------------------ .../Navigation/CustomNavigationBackBar.swift | 18 +++ .../Navigation/NavigationBackButton.swift | 0 .../Presnetaion/TeamBlog/TeamBlogView.swift | 18 +++ .../WebView/WebRepresentableView.swift | 8 ++ .../Sources/Presnetaion/WebView/WebView.swift | 8 ++ 12 files changed, 126 insertions(+), 124 deletions(-) create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/glabal.svg create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/leftArrow.svg create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/link.svg delete mode 100644 TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/NavigationBackButton.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/Contents.json new file mode 100644 index 0000000..64594cb --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "glabal.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/glabal.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/glabal.svg new file mode 100644 index 0000000..a1cd791 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/glabal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/Contents.json new file mode 100644 index 0000000..2a1fcd6 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "leftArrow.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/leftArrow.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/leftArrow.svg new file mode 100644 index 0000000..45f1991 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/leftArrow.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/Contents.json new file mode 100644 index 0000000..6060b67 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "link.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/link.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/link.svg new file mode 100644 index 0000000..f15bf06 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift deleted file mode 100644 index 2ab9d7b..0000000 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/ListRowComponet.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// ListRowComponet.swift -// TeamIntroduce -// -// Created by Wonji Suh on 8/11/25. -// - -import SwiftUI - -struct ListRowComponet: View { - // 기존 프로퍼티 - private var color: Color - private var image: ImageAsset - private var title: String - private var subContent: String - private var blogUrl: String - private var showArrow: Bool - private var moreInfoItem: MoreInfoItem - - // 선택 전달 (부모가 소유) - @Binding private var selection: MoreInfoItem - - // 선택 시 추가 작업이 필요하면 사용 (옵셔널) - private var arrowAction: ((MoreInfoItem) -> Void)? - - init( - color: Color, - image: ImageAsset, - title: String, - subContent: String, - blogUrl: String, - showArrow: Bool, - moreInfoItem: MoreInfoItem, - selection: Binding, - arrowAction: ((MoreInfoItem) -> Void)? = nil - ) { - self.color = color - self.image = image - self.title = title - self.subContent = subContent - self.blogUrl = blogUrl - self.showArrow = showArrow - self.moreInfoItem = moreInfoItem - self._selection = selection - self.arrowAction = arrowAction - } - - var body: some View { - VStack { - HStack { - Circle() - .fill(color) - .frame(width: 35, height: 35) - .overlay { - Image(asset: image) - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - } - - VStack(alignment: .leading, spacing: 0) { - Text(title) - .pretendardFont(family: .SemiBold, size: 14) - .foregroundStyle(.basicBlack) - - Spacer().frame(height: 4) - - Text(subContent) - .pretendardFont(family: .Regular, size: 12) - .foregroundStyle(.textSecondary) - - if !blogUrl.isEmpty { - Text(blogUrl) - .pretendardFont(family: .Regular, size: 10) - .foregroundStyle(.textSecondary100) - } - } - - Spacer() - - if showArrow { - // 👉 버튼으로 만들어 터치영역/접근성 강화 - Button { - selection = moreInfoItem // ✅ 선택 상태 갱신 - arrowAction?(moreInfoItem) // 필요 시 추가 콜백 - } label: { - Image(asset: .rightArrow) - .resizable() - .scaledToFit() - .frame(width: 12, height: 12) - .padding(10) // 터치영역 확장 - } - .buttonStyle(.plain) - .contentShape(Rectangle()) - .accessibilityLabel(Text("\(title) 열기")) - } - } - } - .padding(.vertical, 16) - .padding(.horizontal, 15) - .frame(height: 69) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(.staticWhite) - .shadow(color: .shadowColor, radius: 1) - ) - } -} - -#Preview { - ListRowComponet( - color: .blue10, - image: .people, - title: "팀소개", - subContent: "우리 팀의 특징과 목표", - blogUrl: "", - showArrow: true, - moreInfoItem: .teamBlog, - selection: .constant(.teamBlog), - arrowAction: { item in - - } - ) -} diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift new file mode 100644 index 0000000..5fb317a --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift @@ -0,0 +1,18 @@ +// +// CustomNavigationBackBar.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import SwiftUI + +struct CustomNavigationBackBar: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + CustomNavigationBackBar() +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/NavigationBackButton.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/NavigationBackButton.swift new file mode 100644 index 0000000..e69de29 diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift new file mode 100644 index 0000000..e21e49f --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift @@ -0,0 +1,18 @@ +// +// TeamBlogView.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import SwiftUI + +struct TeamBlogView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + TeamBlogView() +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift new file mode 100644 index 0000000..7205a2b --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift @@ -0,0 +1,8 @@ +// +// WebRepresentableView.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import Foundation diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift new file mode 100644 index 0000000..aba47cf --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift @@ -0,0 +1,8 @@ +// +// WebView.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import Foundation From a7286e768d15232f2601e49dd25e56b73852c1db Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 12:31:20 +0900 Subject: [PATCH 07/20] =?UTF-8?q?=E2=9C=A8[feat]:=20=EB=B8=94=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=9B=B9=EB=B7=B0=20=EC=97=B0=EA=B2=B0=20=EB=B0=8F?= =?UTF-8?q?=20=20=EB=B8=94=EB=A1=9C=EA=B7=B8=20=EB=B7=B0=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Color/Extension+ShapeStyle.swift | 5 +- .../Componet/List/ListRowComponet.swift | 119 ++++++++++ .../Navigation/CustomNavigationBackBar.swift | 43 +++- .../Navigation/NavigationBackButton.swift | 38 ++++ .../DesignSytstem/Image/ImageAsset.swift | 3 + .../Coordinator/Flow/IntroduceRoute.swift | 3 + .../View/IntorduceCoordinatorView.swift | 6 +- .../IntroduceMain/View/ContentView.swift | 21 +- .../Presnetaion/TeamBlog/TeamBlogView.swift | 210 +++++++++++++++++- .../WebView/WebRepresentableView.swift | 139 +++++++++++- .../Sources/Presnetaion/WebView/WebView.swift | 36 ++- 11 files changed, 604 insertions(+), 19 deletions(-) create mode 100644 TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/List/ListRowComponet.swift diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift index bac1789..c128d23 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift @@ -18,8 +18,10 @@ extension ShapeStyle where Self == Color { // MARK: - Static Text static var textPrimary: Color { .init(hex: "FFFFFF") } - static var textSecondary: Color { .init(hex: "717182") } + static var textSecondary: Color { .init(hex: "141414") } static var textSecondary100: Color { .init(hex: "171725") } + static var textGray: Color { .init(hex: "636363") } + static var textGray100: Color { .init(hex: "7D7E8C") } static var textInactive: Color { .init(hex: "70737C47").opacity(0.28) } // MARK: - Static Background @@ -48,6 +50,7 @@ extension ShapeStyle where Self == Color { static var gray90: Color { .init(hex: "202325") } static var grayError: Color { .init(hex: "FF5050") } static var grayWhite: Color { .init(hex: "FFFFFF") } + static var blueGray: Color { .init(hex: "7A7A89") } static var grayPrimary: Color { .init(hex: "0099FF") } // MARK: - Surface diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/List/ListRowComponet.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/List/ListRowComponet.swift new file mode 100644 index 0000000..3633b01 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/List/ListRowComponet.swift @@ -0,0 +1,119 @@ +// +// ListRowComponet.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/11/25. +// + +import SwiftUI + +struct ListRowComponet: View { + private var color: Color + private var image: ImageAsset + private var title: String + private var subContent: String + private var blogUrl: String + private var showArrow: Bool + private var moreInfoItem: MoreInfoItem + + @Binding private var selection: MoreInfoItem + + private var arrowAction: ((MoreInfoItem) -> Void)? + + init( + color: Color, + image: ImageAsset, + title: String, + subContent: String, + blogUrl: String, + showArrow: Bool, + moreInfoItem: MoreInfoItem, + selection: Binding, + arrowAction: ((MoreInfoItem) -> Void)? = nil + ) { + self.color = color + self.image = image + self.title = title + self.subContent = subContent + self.blogUrl = blogUrl + self.showArrow = showArrow + self.moreInfoItem = moreInfoItem + self._selection = selection + self.arrowAction = arrowAction + } + + var body: some View { + VStack { + HStack { + Circle() + .fill(color) + .frame(width: 35, height: 35) + .overlay { + Image(asset: image) + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + } + + VStack(alignment: .leading, spacing: 0) { + Text(title) + .pretendardFont(family: .SemiBold, size: 14) + .foregroundStyle(.basicBlack) + + Spacer().frame(height: 4) + + Text(subContent) + .pretendardFont(family: .Regular, size: 12) + .foregroundStyle(.textSecondary) + + if !blogUrl.isEmpty { + Text(blogUrl) + .pretendardFont(family: .Regular, size: 10) + .foregroundStyle(.textSecondary100) + } + } + + Spacer() + + if showArrow { + Button { + selection = moreInfoItem + arrowAction?(moreInfoItem) + } label: { + Image(asset: .rightArrow) + .resizable() + .scaledToFit() + .frame(width: 12, height: 12) + .padding(10) + } + .buttonStyle(.plain) + .contentShape(Rectangle()) + } + } + } + .padding(.vertical, 16) + .padding(.horizontal, 15) + .frame(height: 69) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.staticWhite) + .shadow(color: .shadowColor, radius: 1) + ) + } +} + +#Preview { + ListRowComponet( + color: .blue10, + image: .people, + title: "팀소개", + subContent: "우리 팀의 특징과 목표", + blogUrl: "", + showArrow: true, + moreInfoItem: .teamBlog, + selection: .constant(.teamBlog), + arrowAction: { item in + + } + ) +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift index 5fb317a..3155a48 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift @@ -7,12 +7,41 @@ import SwiftUI -struct CustomNavigationBackBar: View { - var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) - } -} + struct CustomNavigationBackBar: View { + private var buttonAction: () -> Void = { } + private var text: String + + + init( + text: String, + buttonAction: @escaping () -> Void, + ) { + self.buttonAction = buttonAction + self.text = text + } + + var body: some View { + HStack { + Image(asset: .leftArrow) + .resizable() + .scaledToFit() + .frame(width: 10, height: 20) + .foregroundStyle(.staticWhite) + + Spacer() + .frame(width: 20) -#Preview { - CustomNavigationBackBar() + Text(text) + .pretendardFont(family: .Regular, size: 14) + .foregroundStyle(.textGray) + + Spacer() + + + } + .padding(.horizontal, 30) + .onTapGesture { + buttonAction() + } + } } diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/NavigationBackButton.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/NavigationBackButton.swift index e69de29..7efdc9f 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/NavigationBackButton.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/NavigationBackButton.swift @@ -0,0 +1,38 @@ +// +// NavigationBackButton.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import SwiftUI + + struct NavigationBackButton: View { + var buttonAction: () -> Void = { } + + init( + buttonAction: @escaping () -> Void + ) { + self.buttonAction = buttonAction + } + + var body: some View { + HStack { + Image(asset: .leftArrow) + .resizable() + .scaledToFit() + .frame(width: 10, height: 20) + .foregroundStyle(.gray400) + + + Spacer() + + } + .padding(.horizontal, 20) + .onTapGesture { + buttonAction() + } + } +} + + diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift index 824360e..f6601fe 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift @@ -12,4 +12,7 @@ enum ImageAsset: String { case rightArrow case blog case check + case leftArrow + case glabal + case link } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift index 6a67d16..4d9be14 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift @@ -18,4 +18,7 @@ enum IntroduceRoute: Hashable { // 팀 블로그 case teamBlog + // webView + case webView(url: String) + } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift index d7d94b9..0bccbf2 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift @@ -44,7 +44,11 @@ extension IntorduceCoordinatorView { case .teamIntroduce: EmptyView() case .teamBlog: - EmptyView() + TeamBlogView() + .navigationBarBackButtonHidden() + + case .webView(let url): + WebView(url: url) } } } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift index 12485df..2d0170e 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift @@ -20,10 +20,9 @@ struct ContentView: View { .edgesIgnoringSafeArea(.all) VStack { + Spacer() + Text("main") - .onTapGesture { - coordinator.send(.presntDetail) - } Spacer() @@ -77,10 +76,10 @@ extension ContentView { subContent: item.subtitleContent, blogUrl: "", showArrow: true, - moreInfoItem: moreinfoitem, + moreInfoItem: item, selection: $moreinfoitem, arrowAction: { item in - print("item : \(item)") + handleMoreInfoItem(for: item) } ) .padding(.vertical, 3) @@ -90,8 +89,16 @@ extension ContentView { .padding(.horizontal, 24) } - - + func handleMoreInfoItem(for moreInfo: MoreInfoItem) { + switch moreInfo { + case .teamIntroduce: + coordinator.send(.present(.teamIntroduce)) + case .teamAgreement: + coordinator.send(.present(.teamAgreement)) + case .teamBlog: + coordinator.send(.present(.teamBlog)) + } + } } #Preview { diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift index e21e49f..f737a86 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift @@ -8,11 +8,219 @@ import SwiftUI struct TeamBlogView: View { +@EnvironmentObject var coordinator: IntroduceCoordinator + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + ZStack { + Color.white + .edgesIgnoringSafeArea(.all) + + + VStack { + Spacer() + .frame(height: 14) + + CustomNavigationBackBar(text: "팀블로그") { + coordinator.goBack() + } + + Spacer() + .frame(height: 20) + + blogHeaderView() + + Spacer() + .frame(height: 10) + + blogList() + + + Spacer() + + blogHintBanner() + + Spacer() + .frame(height: 30) + } + } } } +extension TeamBlogView { + + @ViewBuilder + private func blogHeaderView() -> some View { + VStack(alignment: .center) { + Spacer() + .frame(height: 16) + + + Circle() + .fill(.gray40) + .frame(width: 56, height: 56) + .overlay { + Image(asset: .glabal) + .resizable() + .scaledToFit() + .frame(width: 30, height: 30) + + } + + Spacer() + .frame(height: 10) + + HStack { + + Spacer() + + Text("팀원들의 블로그") + .pretendardFont(family: .Regular, size: 13) + .foregroundStyle(.staticBlack) + + Spacer() + } + + Spacer() + .frame(height: 10) + + Text("각자 공부한 내용및 경험을 공유 하는 공간입니다.") + .pretendardFont(family: .Regular, size: 13) + .foregroundStyle(.blueGray) + + Spacer() + .frame(height: 16) + + } + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.staticWhite) + .shadow(color: .shadowColor, radius: 2) + ) + .padding(.horizontal, 16) + + } + + + + @ViewBuilder + private func blogList() -> some View { + VStack { + blogListitem( + name: "김민희", + blogTilte: "모바일개발과크로스플랫폼기술을공유합니다", + blogLink: "https://0minnie0.tistory.com/", + action: { item in + coordinator.send(.present(.webView(url: item))) + } + ) + + blogListitem( + name: "서원지", + blogTilte: "모바일개발과크로스플랫폼기술을공유합니다", + blogLink: "https://velog.io/@suhwj/posts", + action: { item in + coordinator.send(.present(.webView(url: item))) + } + ) + + blogListitem( + name: "홍석현", + blogTilte: "모바일개발과크로스플랫폼기술을공유합니다", + blogLink: "https://velog.io/@gustjrghd/posts", + action: { item in + coordinator.send(.present(.webView(url: item))) + } + ) + } + } + + @ViewBuilder + private func blogListitem( + name: String, + blogTilte: String, + blogLink: String, + action: @escaping (String) -> Void + ) -> some View { + VStack { + HStack { + Circle() + .fill(.gray.opacity(0.3)) + .frame(width: 40, height: 40) + .overlay { + + } + + VStack(alignment: .leading) { + HStack { + Text(name) + .pretendardFont(family: .Regular, size: 12) + .foregroundStyle(.textSecondary) + + Image(asset: .link) + .resizable() + .scaledToFit() + .frame(width: 15, height: 15) + .onTapGesture { + action(blogLink) + } + + + + Spacer() + } + + Text(blogTilte) + .pretendardFont(family: .Regular, size: 12) + .foregroundStyle(.textGray100) + + Text(blogLink) + .pretendardFont(family: .Light, size: 14) + .foregroundStyle(.basicBlack) + .onTapGesture { + action(blogLink) + } + } + + Spacer() + } + .padding(.horizontal, 10) + .padding(.vertical, 16) + + } + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.staticWhite) + .shadow(color: .shadowColor, radius: 2) + ) + .padding(.horizontal, 16) + } + + @ViewBuilder + private func blogHintBanner() -> some View { + VStack { + HStack { + Spacer() + + Text("💡 블로그 링크를 탭하면 새 탭에서 열립니다") + .pretendardFont(family: .Light, size: 12) + .foregroundStyle(.basicBlack) + + Spacer() + } + .padding(.vertical, 16) + } + + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.staticWhite) + .shadow(color: .shadowColor, radius: 2) + ) + .padding(.horizontal, 16) + + } + +} + #Preview { TeamBlogView() } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift index 7205a2b..cc911fa 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift @@ -5,4 +5,141 @@ // Created by Wonji Suh on 8/12/25. // -import Foundation +import SwiftUI +import WebKit + + +import SwiftUI +import WebKit + + struct WebRepresentableView: UIViewRepresentable { + + // MARK: - URL to load + var urlToLoad: String + + init(urlToLoad: String) { + self.urlToLoad = urlToLoad + } + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + func makeUIView(context: Context) -> UIView { + // 컨테이너 + let containerView = UIView() + containerView.backgroundColor = .white + + // WKWebView + let configuration = WKWebViewConfiguration() + let webView = WKWebView(frame: .zero, configuration: configuration) + webView.scrollView.showsVerticalScrollIndicator = false + webView.scrollView.minimumZoomScale = 1.0 + webView.scrollView.maximumZoomScale = 1.0 + webView.navigationDelegate = context.coordinator + webView.uiDelegate = context.coordinator + webView.allowsLinkPreview = true + webView.backgroundColor = .white + webView.translatesAutoresizingMaskIntoConstraints = false + + // ✅ 로딩 인디케이터(스피너) + let spinner = UIActivityIndicatorView(style: .large) + spinner.translatesAutoresizingMaskIntoConstraints = false + spinner.hidesWhenStopped = true + spinner.isHidden = false + + containerView.addSubview(webView) + containerView.addSubview(spinner) + + NSLayoutConstraint.activate([ + // WebView는 전체 + webView.topAnchor.constraint(equalTo: containerView.topAnchor), + webView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), + webView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + webView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), + + // 스피너는 중앙 + spinner.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), + spinner.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), + ]) + + // 코디네이터가 참조 보관 + context.coordinator.webView = webView + context.coordinator.spinner = spinner + + // 로드 직전에 스피너 시작 + spinner.startAnimating() + spinner.alpha = 1 + + // 로드 + _Concurrency.Task { + await loadURLInWebView(urlToLoad: urlToLoad, webView: webView) + } + + return containerView + } + + func loadURLInWebView(urlToLoad: String, webView: WKWebView) async { + guard let url = URL(string: urlToLoad) else { + print("INVALID URL") + return + } + let request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy) + + await MainActor.run { + webView.configuration.upgradeKnownHostsToHTTPS = true + webView.configuration.preferences.minimumFontSize = 16 + webView.load(request) + } + } + + func updateUIView(_ uiView: UIView, context: Context) { + // 필요 시 업데이트 + } + + // MARK: - Coordinator + class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate { + var parent: WebRepresentableView + weak var webView: WKWebView? + weak var spinner: UIActivityIndicatorView? + + init(_ parent: WebRepresentableView) { + self.parent = parent + } + + // MARK: - WKNavigationDelegate + + func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { + // 로딩 시작 → 스피너 표시 + DispatchQueue.main.async { + self.spinner?.alpha = 1 + self.spinner?.startAnimating() + } + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + // 로딩 완료 → 스피너 숨김(페이드아웃) + hideSpinner() + } + + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + hideSpinner() + } + + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + hideSpinner() + } + + private func hideSpinner() { + DispatchQueue.main.async { + guard let spinner = self.spinner else { return } + UIView.animate(withDuration: 0.2, animations: { + spinner.alpha = 0 + }, completion: { _ in + spinner.stopAnimating() + spinner.alpha = 1 // 다음 로딩 대비 초기화 + }) + } + } + } +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift index aba47cf..7fc28cf 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift @@ -5,4 +5,38 @@ // Created by Wonji Suh on 8/12/25. // -import Foundation +import SwiftUI + + struct WebView: View { + + @EnvironmentObject var coordinator: IntroduceCoordinator + var url: String + + init(url: String) { + self.url = url + } + + var body: some View { + ZStack { + Color.white + .edgesIgnoringSafeArea(.all) + + VStack { + Spacer() + .frame(height: 14) + + NavigationBackButton{ + coordinator.goBack() + } + + Spacer() + .frame(height: 16) + + WebRepresentableView(urlToLoad: url) + } + .navigationBarBackButtonHidden(true) + } + } +} + + From de324ab97442aa4109d666ceb05cf1afd4787449 Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 13:58:31 +0900 Subject: [PATCH 08/20] =?UTF-8?q?=F0=9F=AA=9B[chore]:=20=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/auto_assign.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml index 549a69d..71a1396 100644 --- a/.github/auto_assign.yml +++ b/.github/auto_assign.yml @@ -7,6 +7,7 @@ addAssignees: author reviewers: - minneee - Peter1119 + - Roy-wonji # A number of reviewers added to the pull request # Set 0 to add all the reviewers (default: 0) From c5e7bf9c87ace918c4a51c9dd6387fdf2d4dfcfa Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 15:07:42 +0900 Subject: [PATCH 09/20] =?UTF-8?q?=F0=9F=AA=9B[chore]:=20=20PR=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Componet/List/ListRowComponet.swift | 119 --------------- .../Navigation/CustomNavigationBackBar.swift | 8 +- .../Navigation/NavigationBackButton.swift | 38 ----- .../DesignSytstem/Image/ImageAsset.swift | 3 + .../Flow/IntroduceCoordinator.swift | 1 + .../View/IntorduceCoordinatorView.swift | 4 +- .../IntroductionRow/IntroductionRowView.swift | 78 +++++----- .../View/Components/SkeletonRowView.swift | 86 +++++------ .../View/Components/TeamExploreRowView.swift | 143 +++++++++--------- .../IntroduceMain/View/ContentView.swift | 2 +- .../View/IntroductionMainView.swift | 84 +++++----- .../View/IntroductionViewModel.swift | 53 ------- .../ViewModel/IntroductionViewModel.swift | 75 +++++++++ .../Presnetaion/TeamBlog/TeamBlogView.swift | 29 ++-- .../WebView/WebRepresentableView.swift | 11 +- .../Sources/Presnetaion/WebView/WebView.swift | 2 +- 16 files changed, 304 insertions(+), 432 deletions(-) delete mode 100644 TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/List/ListRowComponet.swift delete mode 100644 TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/NavigationBackButton.swift delete mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionViewModel.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/ViewModel/IntroductionViewModel.swift diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/List/ListRowComponet.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/List/ListRowComponet.swift deleted file mode 100644 index 3633b01..0000000 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/List/ListRowComponet.swift +++ /dev/null @@ -1,119 +0,0 @@ -// -// ListRowComponet.swift -// TeamIntroduce -// -// Created by Wonji Suh on 8/11/25. -// - -import SwiftUI - -struct ListRowComponet: View { - private var color: Color - private var image: ImageAsset - private var title: String - private var subContent: String - private var blogUrl: String - private var showArrow: Bool - private var moreInfoItem: MoreInfoItem - - @Binding private var selection: MoreInfoItem - - private var arrowAction: ((MoreInfoItem) -> Void)? - - init( - color: Color, - image: ImageAsset, - title: String, - subContent: String, - blogUrl: String, - showArrow: Bool, - moreInfoItem: MoreInfoItem, - selection: Binding, - arrowAction: ((MoreInfoItem) -> Void)? = nil - ) { - self.color = color - self.image = image - self.title = title - self.subContent = subContent - self.blogUrl = blogUrl - self.showArrow = showArrow - self.moreInfoItem = moreInfoItem - self._selection = selection - self.arrowAction = arrowAction - } - - var body: some View { - VStack { - HStack { - Circle() - .fill(color) - .frame(width: 35, height: 35) - .overlay { - Image(asset: image) - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - } - - VStack(alignment: .leading, spacing: 0) { - Text(title) - .pretendardFont(family: .SemiBold, size: 14) - .foregroundStyle(.basicBlack) - - Spacer().frame(height: 4) - - Text(subContent) - .pretendardFont(family: .Regular, size: 12) - .foregroundStyle(.textSecondary) - - if !blogUrl.isEmpty { - Text(blogUrl) - .pretendardFont(family: .Regular, size: 10) - .foregroundStyle(.textSecondary100) - } - } - - Spacer() - - if showArrow { - Button { - selection = moreInfoItem - arrowAction?(moreInfoItem) - } label: { - Image(asset: .rightArrow) - .resizable() - .scaledToFit() - .frame(width: 12, height: 12) - .padding(10) - } - .buttonStyle(.plain) - .contentShape(Rectangle()) - } - } - } - .padding(.vertical, 16) - .padding(.horizontal, 15) - .frame(height: 69) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(.staticWhite) - .shadow(color: .shadowColor, radius: 1) - ) - } -} - -#Preview { - ListRowComponet( - color: .blue10, - image: .people, - title: "팀소개", - subContent: "우리 팀의 특징과 목표", - blogUrl: "", - showArrow: true, - moreInfoItem: .teamBlog, - selection: .constant(.teamBlog), - arrowAction: { item in - - } - ) -} diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift index 3155a48..42f7c2a 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift @@ -13,7 +13,7 @@ import SwiftUI init( - text: String, + text: String = "", buttonAction: @escaping () -> Void, ) { self.buttonAction = buttonAction @@ -31,9 +31,11 @@ import SwiftUI Spacer() .frame(width: 20) + if !text.isEmpty { Text(text) - .pretendardFont(family: .Regular, size: 14) - .foregroundStyle(.textGray) + .pretendardFont(family: .regular, size: 14) + .foregroundStyle(.textPrimary) + } Spacer() diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/NavigationBackButton.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/NavigationBackButton.swift deleted file mode 100644 index 7efdc9f..0000000 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/NavigationBackButton.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// NavigationBackButton.swift -// TeamIntroduce -// -// Created by Wonji Suh on 8/12/25. -// - -import SwiftUI - - struct NavigationBackButton: View { - var buttonAction: () -> Void = { } - - init( - buttonAction: @escaping () -> Void - ) { - self.buttonAction = buttonAction - } - - var body: some View { - HStack { - Image(asset: .leftArrow) - .resizable() - .scaledToFit() - .frame(width: 10, height: 20) - .foregroundStyle(.gray400) - - - Spacer() - - } - .padding(.horizontal, 20) - .onTapGesture { - buttonAction() - } - } -} - - diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift index f6601fe..1f1e0ca 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift @@ -15,4 +15,7 @@ enum ImageAsset: String { case leftArrow case glabal case link + case TeamAgreementLogo + case TeamBlogLogo + case TeamIntroductionLogo } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift index 4a69d93..6d367c8 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift @@ -73,3 +73,4 @@ final class IntroduceCoordinator: NavigationControlling, ObservableObject { replaceStack([route], animated: animated) } } + diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift index 0bccbf2..12db941 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift @@ -25,7 +25,7 @@ struct IntorduceCoordinatorView : View { var body: some View { NavigationStack(path: $coordinator.path) { - ContentView() + IntroductionMainView(viewModel: IntroductionViewModel(coordinator: coordinator)) .navigationDestination(for: IntroduceRoute.self, destination: makeDestination) } .modelContainer(sharedModelContainer) @@ -38,7 +38,7 @@ extension IntorduceCoordinatorView { private func makeDestination(for route: IntroduceRoute) -> some View { switch route { case .introduceMain: - ContentView() + IntroductionMainView(viewModel: IntroductionViewModel(coordinator: coordinator)) case .teamAgreement: ContentView() case .teamIntroduce: diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift index 80652e8..00c6862 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift @@ -8,48 +8,48 @@ import SwiftUI struct IntroductionRowView: View { - private let model: IntroductionRowModel - - init(model: IntroductionRowModel) { - self.model = model - } - - var body: some View { - HStack { - Circle() - .frame(width: 42, height: 42) - VStack(alignment: .leading, spacing: 4) { - HStack(spacing: 4) { - Text(model.name) - .pretendardFont(family: .semiBold, size: 14) - .foregroundStyle(.textPrimary) - - if model.isLeader { - Image(systemName: "crown.fill") - .foregroundStyle(Color.yellow) - .font(.system(size: 12)) - } - } - - Text(model.role) - .pretendardFont(family: .regular, size: 12) - .foregroundStyle(.textSecondary) - - MBTILabel(mbti: model.mbti) - } - - Spacer() - - Image("ArrowRight") - .foregroundStyle(Color(hex: "717182")) + private let model: IntroductionRowModel + + init(model: IntroductionRowModel) { + self.model = model + } + + var body: some View { + HStack { + Circle() + .frame(width: 42, height: 42) + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 4) { + Text(model.name) + .pretendardFont(family: .semiBold, size: 14) + .foregroundStyle(.textPrimary) + + if model.isLeader { + Image(systemName: "crown.fill") + .foregroundStyle(Color.yellow) + .font(.system(size: 12)) + } } - .padding(16) - .background(Color.staticWhite) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .shadow(radius: 1) + + Text(model.role) + .pretendardFont(family: .regular, size: 12) + .foregroundStyle(.textSecondary) + + MBTILabel(mbti: model.mbti) + } + + Spacer() + + Image("ArrowRight") + .foregroundStyle(Color(hex: "717182")) } + .padding(16) + .background(Color.staticWhite) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .shadow(radius: 1) + } } #Preview { - IntroductionRowView(model: IntroductionRowModel.mockData[0]) + IntroductionRowView(model: IntroductionRowModel.mockData[0]) } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/SkeletonRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/SkeletonRowView.swift index efa7ce5..ac20e9b 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/SkeletonRowView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/SkeletonRowView.swift @@ -8,51 +8,51 @@ import SwiftUI struct SkeletonRowView: View { - @State private var isAnimating = false - - var body: some View { - HStack { - Circle() - .frame(width: 42, height: 42) - .foregroundColor(.gray.opacity(0.3)) - - VStack(alignment: .leading, spacing: 4) { - RoundedRectangle(cornerRadius: 4) - .frame(width: 80, height: 16) - .foregroundColor(.gray.opacity(0.3)) - - RoundedRectangle(cornerRadius: 4) - .frame(width: 120, height: 12) - .foregroundColor(.gray.opacity(0.2)) - - RoundedRectangle(cornerRadius: 8) - .frame(width: 50, height: 20) - .foregroundColor(.gray.opacity(0.2)) - } - - Spacer() - - RoundedRectangle(cornerRadius: 4) - .frame(width: 16, height: 16) - .foregroundColor(.gray.opacity(0.2)) - } - .padding(16) - .background(Color.staticWhite) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .shadow(radius: 1) - .opacity(isAnimating ? 0.5 : 1.0) - .animation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true), value: isAnimating) - .onAppear { - isAnimating = true - } + @State private var isAnimating = false + + var body: some View { + HStack { + Circle() + .frame(width: 42, height: 42) + .foregroundColor(.gray.opacity(0.3)) + + VStack(alignment: .leading, spacing: 4) { + RoundedRectangle(cornerRadius: 4) + .frame(width: 80, height: 16) + .foregroundColor(.gray.opacity(0.3)) + + RoundedRectangle(cornerRadius: 4) + .frame(width: 120, height: 12) + .foregroundColor(.gray.opacity(0.2)) + + RoundedRectangle(cornerRadius: 8) + .frame(width: 50, height: 20) + .foregroundColor(.gray.opacity(0.2)) + } + + Spacer() + + RoundedRectangle(cornerRadius: 4) + .frame(width: 16, height: 16) + .foregroundColor(.gray.opacity(0.2)) } + .padding(16) + .background(Color.staticWhite) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .shadow(radius: 1) + .opacity(isAnimating ? 0.5 : 1.0) + .animation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true), value: isAnimating) + .onAppear { + isAnimating = true + } + } } #Preview { - VStack { - SkeletonRowView() - SkeletonRowView() - SkeletonRowView() - } - .padding() + VStack { + SkeletonRowView() + SkeletonRowView() + SkeletonRowView() + } + .padding() } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift index fa7649c..977e303 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift @@ -8,85 +8,86 @@ import SwiftUI enum TeamExploreItem: CaseIterable, Identifiable { - case introduction - case agreement - case blog - - var id: Self { self } - - - var title: String { - switch self { - case .introduction: - return "팀 소개" - case .agreement: - return "팀 약속" - case .blog: - return "팀 블로그" - } + case introduction + case agreement + case blog + + var id: Self { self } + + + var title: String { + switch self { + case .introduction: + return "팀 소개" + case .agreement: + return "팀 약속" + case .blog: + return "팀 블로그" } - - var subtitle: String { - switch self { - case .introduction: - return "우리 팀의 특징과 목표" - case .agreement: - return "함께 지켜나갈 소중한 약속들" - case .blog: - return "팀원들의 블로그 모음" - } + } + + var subtitle: String { + switch self { + case .introduction: + return "우리 팀의 특징과 목표" + case .agreement: + return "함께 지켜나갈 소중한 약속들" + case .blog: + return "팀원들의 블로그 모음" } - - var imageName: String { - switch self { - case .introduction: - return "TeamIntroductionLogo" - case .agreement: - return "TeamAgreementLogo" - case .blog: - return "TeamBlogLogo" - } + } + + var imageName: ImageAsset { + switch self { + case .introduction: + return .TeamIntroductionLogo + case .agreement: + return .TeamAgreementLogo + case .blog: + return .TeamBlogLogo } + } } struct TeamExploreRowView: View { - - private let item: TeamExploreItem - - init(item: TeamExploreItem) { - self.item = item - } - - var body: some View { - HStack { - Image(item.imageName) - .frame(width: 42, height: 42) - VStack(alignment: .leading, spacing: 4) { - Text(item.title) - .pretendardFont(family: .semiBold, size: 14) - .foregroundStyle(.textPrimary) - - Text(item.subtitle) - .pretendardFont(family: .regular, size: 12) - .foregroundStyle(.textSecondary) - } - - Spacer() - - Image("ArrowRight") - .foregroundStyle(Color(hex: "717182")) - } - .padding(16) - .background(Color.staticWhite) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .shadow(radius: 1) + + private let item: TeamExploreItem + + init(item: TeamExploreItem) { + self.item = item + } + + var body: some View { + HStack { + Image(asset: item.imageName) + .frame(width: 42, height: 42) + + VStack(alignment: .leading, spacing: 4) { + Text(item.title) + .pretendardFont(family: .semiBold, size: 14) + .foregroundStyle(.textPrimary) + + Text(item.subtitle) + .pretendardFont(family: .regular, size: 12) + .foregroundStyle(.textSecondary) + } + + Spacer() + + Image("ArrowRight") + .foregroundStyle(Color(hex: "717182")) } + .padding(16) + .background(Color.staticWhite) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .shadow(radius: 1) + } } #Preview { - VStack { - TeamExploreRowView(item: .introduction) - TeamExploreRowView(item: .agreement) - TeamExploreRowView(item: .blog) - } + VStack { + TeamExploreRowView(item: .introduction) + TeamExploreRowView(item: .agreement) + TeamExploreRowView(item: .blog) + } } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift index 2d0170e..20c4eb5 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift @@ -57,7 +57,7 @@ extension ContentView { VStack { HStack { Text("더 알아보기") - .pretendardFont(family:.SemiBold, size: 20) + .pretendardFont(family:.semiBold, size: 20) .foregroundStyle(.basicBlack) Spacer() diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionMainView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionMainView.swift index af42da3..6b793b1 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionMainView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionMainView.swift @@ -8,54 +8,58 @@ import SwiftUI struct IntroductionMainView: View { - @ObservedObject var viewModel: IntroductionViewModel - + @State private var viewModel: IntroductionViewModel + init(viewModel: IntroductionViewModel) { - self.viewModel = viewModel + _viewModel = State(initialValue: viewModel) } - var body: some View { - ScrollView { - VStack(spacing: 16) { - MissionView() - - VStack { - Text("팀원 소개") - .pretendardFont(family: .bold, size: 14) - .foregroundStyle(.textPrimary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 8) - if viewModel.isLoading { - ForEach(0..<3, id: \.self) { _ in - SkeletonRowView() - } - } else { - ForEach(viewModel.introductions) { model in - IntroductionRowView(model: model) - } - } - } - - VStack { - Text("더 알아보기") - .pretendardFont(family: .bold, size: 14) - .foregroundStyle(.textPrimary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 8) - - ForEach(TeamExploreItem.allCases) { item in - TeamExploreRowView(item: item) - } - } + + var body: some View { + ScrollView { + VStack(spacing: 16) { + MissionView() + + VStack { + Text("팀원 소개") + .pretendardFont(family: .bold, size: 14) + .foregroundStyle(.textPrimary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 8) + if viewModel.isLoading { + ForEach(0..<3, id: \.self) { _ in + SkeletonRowView() } - .padding(.horizontal, 16) - .onAppear { - viewModel.onAppear() + } else { + ForEach(viewModel.introductions) { model in + IntroductionRowView(model: model) } + } } + + VStack { + Text("더 알아보기") + .pretendardFont(family: .bold, size: 14) + .foregroundStyle(.textPrimary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 8) + + ForEach(TeamExploreItem.allCases) { item in + TeamExploreRowView(item: item) + .onTapGesture { + viewModel.send(.tapMoreInfo(item)) + } + } + } + } + .padding(.horizontal, 16) + } + .onAppear { + viewModel.send(.onAppear) } + } } #Preview { - IntroductionMainView(viewModel: IntroductionViewModel()) + IntroductionMainView(viewModel: IntroductionViewModel()) } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionViewModel.swift deleted file mode 100644 index 4fbf61a..0000000 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionViewModel.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// IntroductionViewModel.swift -// TeamIntroduce -// -// Created by 홍석현 on 8/11/25. -// - -import Foundation -import SwiftUI - -@MainActor -final class IntroductionViewModel: ObservableObject { - - // MARK: - Published Properties - @Published private(set) var introductions: [IntroductionRowModel] = [] - @Published private(set) var isLoading = false - - // MARK: - Public Methods - - /// onAppear 시 호출되는 데이터 페치 메서드 - func onAppear() { - Task { - await fetchIntroductions() - } - } - - /// 데이터를 새로고침하는 메서드 - func refresh() { - Task { - await fetchIntroductions() - } - } - - // MARK: - Private Methods - - /// 팀원 소개 데이터를 가져오는 메서드 - private func fetchIntroductions() async { - isLoading = true - - do { - // 실제 API 호출 시뮬레이션 (2초 딜레이) - try await Task.sleep(nanoseconds: 2_000_000_000) - - // Mock 데이터 로드 - introductions = IntroductionRowModel.mockData - - } catch { - introductions = [] - } - - isLoading = false - } -} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/ViewModel/IntroductionViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/ViewModel/IntroductionViewModel.swift new file mode 100644 index 0000000..4295258 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/ViewModel/IntroductionViewModel.swift @@ -0,0 +1,75 @@ +// +// IntroductionViewModel.swift +// TeamIntroduce +// +// Created by 홍석현 on 8/11/25. +// + +import Foundation +import SwiftUI + +@MainActor +@Observable +final class IntroductionViewModel { + + // MARK: - State + private(set) var introductions: [IntroductionRowModel] = [] + private(set) var isLoading = false + + // 네비게이션을 코디네이터로 위임하는 라우팅 클로저 + private let route: (IntroduceCoordinator.Action) -> Void + + // MARK: - Action (뷰에서 이 enum만 쓰면 됩니다) + enum Action { + case onAppear + case refresh + case tapMoreInfo(TeamExploreItem) + } + + // MARK: - Init + /// 기본값: no-op(코디네이터 주입 안 해도 안전) + init(route: @escaping (IntroduceCoordinator.Action) -> Void = { _ in }) { + self.route = route + } + + /// 편의 이니셜라이저: 코디네이터 주입 + convenience init(coordinator: IntroduceCoordinator?) { + if let coordinator { + self.init(route: { [weak coordinator] action in coordinator?.send(action) }) + } else { + self.init() // no-op + } + } + + // MARK: - Single entrypoint + func send(_ action: Action) { + switch action { + case .onAppear, .refresh: + Task { await fetchIntroductions() } + + case .tapMoreInfo(let moreInfo): + switch moreInfo { + case .introduction: + route(.present(.teamIntroduce)) + case .agreement: + route(.present(.teamAgreement)) + case .blog: + route(.present(.teamBlog)) + } + } + } + + // MARK: - Private + private func fetchIntroductions() async { + isLoading = true + defer { isLoading = false } + + do { + // mock delay + try await Task.sleep(nanoseconds: 2_000_000_000) + introductions = IntroductionRowModel.mockData + } catch { + introductions = [] + } + } +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift index f737a86..d467030 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift @@ -15,7 +15,6 @@ struct TeamBlogView: View { Color.white .edgesIgnoringSafeArea(.all) - VStack { Spacer() .frame(height: 14) @@ -74,7 +73,7 @@ extension TeamBlogView { Spacer() Text("팀원들의 블로그") - .pretendardFont(family: .Regular, size: 13) + .pretendardFont(family: .regular, size: 13) .foregroundStyle(.staticBlack) Spacer() @@ -84,7 +83,7 @@ extension TeamBlogView { .frame(height: 10) Text("각자 공부한 내용및 경험을 공유 하는 공간입니다.") - .pretendardFont(family: .Regular, size: 13) + .pretendardFont(family: .regular, size: 13) .foregroundStyle(.blueGray) Spacer() @@ -107,7 +106,7 @@ extension TeamBlogView { VStack { blogListitem( name: "김민희", - blogTilte: "모바일개발과크로스플랫폼기술을공유합니다", + blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", blogLink: "https://0minnie0.tistory.com/", action: { item in coordinator.send(.present(.webView(url: item))) @@ -116,7 +115,7 @@ extension TeamBlogView { blogListitem( name: "서원지", - blogTilte: "모바일개발과크로스플랫폼기술을공유합니다", + blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", blogLink: "https://velog.io/@suhwj/posts", action: { item in coordinator.send(.present(.webView(url: item))) @@ -125,7 +124,7 @@ extension TeamBlogView { blogListitem( name: "홍석현", - blogTilte: "모바일개발과크로스플랫폼기술을공유합니다", + blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", blogLink: "https://velog.io/@gustjrghd/posts", action: { item in coordinator.send(.present(.webView(url: item))) @@ -137,7 +136,7 @@ extension TeamBlogView { @ViewBuilder private func blogListitem( name: String, - blogTilte: String, + blogTitle: String, blogLink: String, action: @escaping (String) -> Void ) -> some View { @@ -153,7 +152,7 @@ extension TeamBlogView { VStack(alignment: .leading) { HStack { Text(name) - .pretendardFont(family: .Regular, size: 12) + .pretendardFont(family: .regular, size: 12) .foregroundStyle(.textSecondary) Image(asset: .link) @@ -164,17 +163,15 @@ extension TeamBlogView { action(blogLink) } - - Spacer() } - Text(blogTilte) - .pretendardFont(family: .Regular, size: 12) + Text(blogTitle) + .pretendardFont(family: .regular, size: 12) .foregroundStyle(.textGray100) Text(blogLink) - .pretendardFont(family: .Light, size: 14) + .pretendardFont(family: .light, size: 14) .foregroundStyle(.basicBlack) .onTapGesture { action(blogLink) @@ -182,9 +179,9 @@ extension TeamBlogView { } Spacer() + } - .padding(.horizontal, 10) - .padding(.vertical, 16) + .padding(16) } .background( @@ -202,7 +199,7 @@ extension TeamBlogView { Spacer() Text("💡 블로그 링크를 탭하면 새 탭에서 열립니다") - .pretendardFont(family: .Light, size: 12) + .pretendardFont(family: .light, size: 12) .foregroundStyle(.basicBlack) Spacer() diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift index cc911fa..8ca5a64 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift @@ -42,7 +42,6 @@ import WebKit webView.backgroundColor = .white webView.translatesAutoresizingMaskIntoConstraints = false - // ✅ 로딩 인디케이터(스피너) let spinner = UIActivityIndicatorView(style: .large) spinner.translatesAutoresizingMaskIntoConstraints = false spinner.hidesWhenStopped = true @@ -111,9 +110,9 @@ import WebKit func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { // 로딩 시작 → 스피너 표시 - DispatchQueue.main.async { - self.spinner?.alpha = 1 - self.spinner?.startAnimating() + DispatchQueue.main.async { [weak self ] in + self?.spinner?.alpha = 1 + self?.spinner?.startAnimating() } } @@ -131,8 +130,8 @@ import WebKit } private func hideSpinner() { - DispatchQueue.main.async { - guard let spinner = self.spinner else { return } + DispatchQueue.main.async { [weak self ] in + guard let spinner = self?.spinner else { return } UIView.animate(withDuration: 0.2, animations: { spinner.alpha = 0 }, completion: { _ in diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift index 7fc28cf..86027ee 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift @@ -25,7 +25,7 @@ import SwiftUI Spacer() .frame(height: 14) - NavigationBackButton{ + CustomNavigationBackBar(text: ""){ coordinator.goBack() } From d44ac45d088363b804215d22b2a100f339bbc61d Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 15:10:40 +0900 Subject: [PATCH 10/20] =?UTF-8?q?=F0=9F=AA=9B[chore]:=20=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Flow/IntroduceCoordinator.swift | 1 + .../View/Components/TeamExploreItem.swift | 50 +++++++++++++++++++ .../View/Components/TeamExploreRowView.swift | 41 --------------- 3 files changed, 51 insertions(+), 41 deletions(-) create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreItem.swift diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift index 6d367c8..d457bf8 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift @@ -74,3 +74,4 @@ final class IntroduceCoordinator: NavigationControlling, ObservableObject { } } + diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreItem.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreItem.swift new file mode 100644 index 0000000..53531c3 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreItem.swift @@ -0,0 +1,50 @@ +// +// TeamExploreItem.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import Foundation + +enum TeamExploreItem: CaseIterable, Identifiable { + case introduction + case agreement + case blog + + var id: Self { self } + + + var title: String { + switch self { + case .introduction: + return "팀 소개" + case .agreement: + return "팀 약속" + case .blog: + return "팀 블로그" + } + } + + var subtitle: String { + switch self { + case .introduction: + return "우리 팀의 특징과 목표" + case .agreement: + return "함께 지켜나갈 소중한 약속들" + case .blog: + return "팀원들의 블로그 모음" + } + } + + var imageName: ImageAsset { + switch self { + case .introduction: + return .TeamIntroductionLogo + case .agreement: + return .TeamAgreementLogo + case .blog: + return .TeamBlogLogo + } + } +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift index 977e303..1bce1c2 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift @@ -7,47 +7,6 @@ import SwiftUI -enum TeamExploreItem: CaseIterable, Identifiable { - case introduction - case agreement - case blog - - var id: Self { self } - - - var title: String { - switch self { - case .introduction: - return "팀 소개" - case .agreement: - return "팀 약속" - case .blog: - return "팀 블로그" - } - } - - var subtitle: String { - switch self { - case .introduction: - return "우리 팀의 특징과 목표" - case .agreement: - return "함께 지켜나갈 소중한 약속들" - case .blog: - return "팀원들의 블로그 모음" - } - } - - var imageName: ImageAsset { - switch self { - case .introduction: - return .TeamIntroductionLogo - case .agreement: - return .TeamAgreementLogo - case .blog: - return .TeamBlogLogo - } - } -} struct TeamExploreRowView: View { From dfd01b26f4eba559391f1eac2f66363add48fadb Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 15:46:13 +0900 Subject: [PATCH 11/20] =?UTF-8?q?=F0=9F=AA=9B[chore]:=20=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=A1=9C=EB=94=A9=20=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=20=EC=B6=94=EA=B0=80=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/IntorduceCoordinatorView.swift | 2 +- .../IntroduceMain/View/ContentView.swift | 47 ------- .../IntroduceMain/View/MoreInfoItem.swift | 66 ---------- .../ViewModel/IntroductionViewModel.swift | 1 + .../Sources/Presnetaion/Root/RootView.swift | 2 + .../Presnetaion/TeamBlog/TeamBlogView.swift | 122 ++++++++++-------- .../TeamBlog/TeamBlogViewModel.swift | 64 +++++++++ 7 files changed, 137 insertions(+), 167 deletions(-) delete mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/MoreInfoItem.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift index 12db941..73316b9 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift @@ -44,7 +44,7 @@ extension IntorduceCoordinatorView { case .teamIntroduce: EmptyView() case .teamBlog: - TeamBlogView() + TeamBlogView(viewModel: .init(coordinator: coordinator)) .navigationBarBackButtonHidden() case .webView(let url): diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift index 20c4eb5..0a8d72b 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift @@ -12,7 +12,6 @@ struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] @EnvironmentObject private var coordinator: IntroduceCoordinator - @State private var moreinfoitem: MoreInfoItem = .teamIntroduce var body: some View { ZStack { @@ -26,7 +25,6 @@ struct ContentView: View { Spacer() - moreInfoSection() Spacer() .frame(height: 20) @@ -52,53 +50,8 @@ struct ContentView: View { extension ContentView { - @ViewBuilder - private func moreInfoSection() -> some View { - VStack { - HStack { - Text("더 알아보기") - .pretendardFont(family:.semiBold, size: 20) - .foregroundStyle(.basicBlack) - Spacer() - - } - - Spacer() - .frame(height: 14) - - ForEach(MoreInfoItem.moreInfoList, id: \.self) { item in - VStack { - ListRowComponet( - color: item.color, - image: item.images, - title: item.titleContent, - subContent: item.subtitleContent, - blogUrl: "", - showArrow: true, - moreInfoItem: item, - selection: $moreinfoitem, - arrowAction: { item in - handleMoreInfoItem(for: item) - } - ) - .padding(.vertical, 3) - } - } - } - .padding(.horizontal, 24) - } - func handleMoreInfoItem(for moreInfo: MoreInfoItem) { - switch moreInfo { - case .teamIntroduce: - coordinator.send(.present(.teamIntroduce)) - case .teamAgreement: - coordinator.send(.present(.teamAgreement)) - case .teamBlog: - coordinator.send(.present(.teamBlog)) - } - } } #Preview { diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/MoreInfoItem.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/MoreInfoItem.swift deleted file mode 100644 index 1e2c4ee..0000000 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/MoreInfoItem.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// MoreInfoItem.swift -// TeamIntroduce -// -// Created by Wonji Suh on 8/11/25. -// - -import Foundation -import SwiftUI - -enum MoreInfoItem: String , CaseIterable , Identifiable { - case teamIntroduce - case teamAgreement - case teamBlog - var id: String { rawValue } - - var titleContent: String { - switch self { - case .teamIntroduce: - return "팀 소개" - case .teamAgreement: - return "팀 약속" - case .teamBlog: - return "팀 블로그" - } - } - - var subtitleContent: String { - switch self { - case .teamIntroduce: - return"우리 팀의 특징과 목표" - case .teamAgreement: - return "함께 지켜나갈 소중한 약속들" - case .teamBlog: - return "팀원들의 블로그 모음" - } - } - - var images: ImageAsset { - switch self { - case .teamIntroduce: - return .people - case .teamAgreement: - return .check - case .teamBlog: - return .blog - } - } - - var color: Color { - switch self { - case .teamIntroduce: - return .blue10.opacity(0.1) - case .teamAgreement: - return .green.opacity(0.1) - case .teamBlog: - return .lightPurple.opacity(0.1) - } - } - - - static var moreInfoList: [MoreInfoItem] { - [.teamIntroduce, .teamAgreement, .teamBlog] - } - -} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/ViewModel/IntroductionViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/ViewModel/IntroductionViewModel.swift index 4295258..0bc1c23 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/ViewModel/IntroductionViewModel.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/ViewModel/IntroductionViewModel.swift @@ -7,6 +7,7 @@ import Foundation import SwiftUI +import Combine @MainActor @Observable diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift index f1ce0c8..eaad56c 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift @@ -18,3 +18,5 @@ struct RootView: View { #Preview { RootView() } + + diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift index d467030..5e2b6fa 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift @@ -8,41 +8,51 @@ import SwiftUI struct TeamBlogView: View { -@EnvironmentObject var coordinator: IntroduceCoordinator + @EnvironmentObject var coordinator: IntroduceCoordinator + @Bindable var viewModel: TeamBlogViewModel - var body: some View { - ZStack { - Color.white - .edgesIgnoringSafeArea(.all) - VStack { - Spacer() - .frame(height: 14) + init(viewModel: TeamBlogViewModel) { + self.viewModel = viewModel + } - CustomNavigationBackBar(text: "팀블로그") { - coordinator.goBack() - } + var body: some View { + ZStack { + Color.white + .edgesIgnoringSafeArea(.all) - Spacer() - .frame(height: 20) + VStack { + Spacer() + .frame(height: 14) - blogHeaderView() + CustomNavigationBackBar(text: "팀블로그") { + coordinator.goBack() + } - Spacer() - .frame(height: 10) + Spacer() + .frame(height: 20) - blogList() + blogHeaderView() + Spacer() + .frame(height: 10) - Spacer() + blogList() - blogHintBanner() - Spacer() - .frame(height: 30) - } + Spacer() + + blogHintBanner() + + Spacer() + .frame(height: 30) } + + } + .onAppear { + viewModel.send(.onAppear) } + } } extension TeamBlogView { @@ -54,7 +64,7 @@ extension TeamBlogView { .frame(height: 16) - Circle() + Circle() .fill(.gray40) .frame(width: 56, height: 56) .overlay { @@ -103,33 +113,39 @@ extension TeamBlogView { @ViewBuilder private func blogList() -> some View { - VStack { - blogListitem( - name: "김민희", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://0minnie0.tistory.com/", - action: { item in - coordinator.send(.present(.webView(url: item))) - } - ) - - blogListitem( - name: "서원지", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://velog.io/@suhwj/posts", - action: { item in - coordinator.send(.present(.webView(url: item))) - } - ) - - blogListitem( - name: "홍석현", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://velog.io/@gustjrghd/posts", - action: { item in - coordinator.send(.present(.webView(url: item))) - } - ) + if !viewModel.isLoading { + VStack { + blogListitem( + name: "김민희", + blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", + blogLink: "https://0minnie0.tistory.com/", + action: { item in + viewModel.send(.presentWebView(url: item)) + } + ) + + blogListitem( + name: "서원지", + blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", + blogLink: "https://velog.io/@suhwj/posts", + action: { item in + viewModel.send(.presentWebView(url: item)) + } + ) + + blogListitem( + name: "홍석현", + blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", + blogLink: "https://velog.io/@gustjrghd/posts", + action: { item in + viewModel.send(.presentWebView(url: item)) + } + ) + } + } else { + ForEach(0..<3, id: \.self) { _ in + SkeletonRowView() + } } } @@ -146,7 +162,7 @@ extension TeamBlogView { .fill(.gray.opacity(0.3)) .frame(width: 40, height: 40) .overlay { - + } VStack(alignment: .leading) { @@ -178,7 +194,7 @@ extension TeamBlogView { } } - Spacer() + Spacer() } .padding(16) @@ -219,5 +235,5 @@ extension TeamBlogView { } #Preview { - TeamBlogView() + TeamBlogView(viewModel: .init()) } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift new file mode 100644 index 0000000..1886836 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift @@ -0,0 +1,64 @@ +// +// TeamBlogViewModel.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import SwiftUI +import Combine + +@MainActor +@Observable +final class TeamBlogViewModel { + private(set) var isLoading = false + + private let route: (IntroduceCoordinator.Action) -> Void + + + + // MARK: - Init + /// 기본값: no-op(코디네이터 주입 안 해도 안전) + init(route: @escaping (IntroduceCoordinator.Action) -> Void = { _ in }) { + self.route = route + } + + /// 편의 이니셜라이저: 코디네이터 주입 + convenience init(coordinator: IntroduceCoordinator?) { + if let coordinator { + self.init(route: { [weak coordinator] action in coordinator?.send(action) }) + } else { + self.init() // no-op + } + } + + + // MARK: - Action (뷰에서 이 enum만 쓰면 됩니다) + enum Action { + case onAppear + case refresh + + case presentWebView(url: String) + } + + // MARK: - Single entrypoint + func send(_ action: Action) { + switch action { + case .onAppear, .refresh: + Task { await fetchIntroductions() } + + case .presentWebView(let url): + route(.present(.webView(url: url))) + } + } + + // MARK: - Private + private func fetchIntroductions() async { + isLoading = true + Task { + try await Task.sleep(for: .seconds(1)) + + isLoading = false + } + } +} From 39f9e6b967b4adee8b3448fa9ff12b49334bd8b4 Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 19:15:13 +0900 Subject: [PATCH 12/20] =?UTF-8?q?=E2=9C=A8[feat]:=20=ED=8C=80=EC=86=8C?= =?UTF-8?q?=EA=B0=9C=20=20=EB=B7=B0=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resources/Assets.xcassets/Contents.json | 3 + .../TeamiIntroduce.imageset/Contents.json | 21 +++ .../TeamiIntroduce.png | Bin 0 -> 5072 bytes .../Contents.json | 21 +++ .../TeamInfroduce_Accident.png | Bin 0 -> 2379 bytes .../Contents.json | 21 +++ .../TeamInfroduce_Circle.png | Bin 0 -> 2714 bytes .../Contents.json | 21 +++ .../TeamInfroduce_Heart.png | Bin 0 -> 2500 bytes .../Contents.json | 21 +++ .../TeamInfroduce_Person.png | Bin 0 -> 2619 bytes .../DesignSytstem/Image/ImageAsset.swift | 5 + .../View/IntorduceCoordinatorView.swift | 3 +- .../TeamIntroduce/TeamIntroduceView.swift | 178 ++++++++++++++++++ .../TeamIntroduce/TypingText.swift | 62 ++++++ 15 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/TeamiIntroduce.png create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TypingText.swift diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json index 73c0059..7f73912 100644 --- a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json @@ -2,5 +2,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "compression-type" : "automatic" } } diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/Contents.json new file mode 100644 index 0000000..98b4695 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "TeamiIntroduce.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/TeamiIntroduce.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/TeamiIntroduce.png new file mode 100644 index 0000000000000000000000000000000000000000..fffc4cba806aa2df087a6cf692224ba50b3f080b GIT binary patch literal 5072 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 literal 0 HcmV?d00001 diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json new file mode 100644 index 0000000..a67eb54 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "TeamInfroduce_Circle.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png new file mode 100644 index 0000000000000000000000000000000000000000..3cb6fcf2ff289912017dfe2888ab002dab9787cb GIT binary patch literal 2714 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 U&;|acqW}N^07*qoM6N<$g5pvL0{{R3 literal 0 HcmV?d00001 diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json new file mode 100644 index 0000000..c0f5096 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "TeamInfroduce_Heart.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png new file mode 100644 index 0000000000000000000000000000000000000000..600341c98648a040d052bcd4038b3be3de9ac7ac GIT binary patch literal 2500 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 O0000500009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yPgLg`M~ze|@c866HscT>}*u1GkvUvF5qlPVMhB0wS@yKkM4iGv7o0Pm!@YN?ilRRE&U^nAb<>`f#RRF)6~6M40r6@V&{M*mr}WnEC-~&zh8$yrXLSEdvn>WBFVB)2BPY0wX3{FV1I%nOF|-cVIdMhC>rZ`i&MJDtiM| z7`3V|D&$fJ>lDubRoYb6l&D5Cp{k_L!I1KKJ*81x2i(2^*Dh(kc{VuP@|K#F6L z*a(^E^jhWd(1?xkD*+MJC2Y`KF1I`24fdnt3L+sTVM<3YAOpg&qkBV@UIv%T%@;H^ zH$oCcD98=@UQ}neoy^Y8_If70>qTjya|tS11Y@z(!qux+VfVA&1fSmr zJuklkuO9gc_=5pBbnrzu`{xfpgZuB^y#tYW9A5wR>u~(|acDq5C=D!iT*jS%d(vw` z{yhwbP*fegH#s)76J3ybqI$M`J3D_#qXoTQ1IBghxXvCsN1(OU!PmL*xLO=7kS{10 zrRSj9WP<2i6rSqX4ii&T@ZiA^*qfUn8czT%-2E#-`CN?6?hY+WkBL_1>y+>8k-v7op*kR#1;6*aGF`>M2AuLG}QR)zXYu&O96lMoP z(TXk=`Z&voy4o_iMajFplR<72hi zx=1`4eL>VnD~5P>G^s=yc5L4U+jn$8-|bs)@W3Gm`KMtn=!e6-J@B8;u0v;MC+yg^ z6}+hBLx&E-o4@@Hw6(QDZ_i=C%*_ZbAF>6mV8~{&`-NHmER84Q3I;8rCLda%QKoW9 zWxw{HOqA^a>{6FB#}xC(sI+gTKI}%Y*S`28R#4UO)JKe(+3t;q>X#pwa5! zqYux)hv&}2zVAK{f!Pqe_VZuC&6_tdFrGvWTS|FuQCWhKe`aF*7O(b`@pOK2!bM$N zJJZzb@zMZ#8M~g{y_-unJUmR%h-oB|rQv9r&V`dSgkvs}rjb~NuXVZId~IjvGyFZ0 zDDeB1_I8TNm9iSe=MPf1%Uje8)5@fqCgLf2;=~EFnw!4Bqk39frR7d9B6^Li1!J`V z%r(_$6FZ^5uMdKuBu3vH7TRhMquds-EM-|AYR!7EU>2{dtApW34uXRu!BOXAomMMzFa>l|YkeJQ^I`RRjPr99z52 zgF3wq%^}9q3u;73XVRo?vqSBpcu~GwcQ8Ht(7C~AFcd97m=8~MnL41NG+FRTDCZuI ztg;_bGMOxCZL@n2NFyxM9Te>emktaJw6;I7ITA~zSmmmy?J6xcWJ_|)vN=bf^BSZS zD&CZA5lHyPjT>#H?aE5Dcpr?(P>YS+$iuIk4JM;O;r=ds?Urr9MazW}|0P;)jLG zZEOAtfq-`7{le6kXFcP>+72jJJ}Y4<7E;XfSom*fYM2cK0|%^D>up%&R|v8qy&`In zxg8E$t+l$R+0i&yTUS%`8m1?OySl>N9q(+8|2~6FN4>>TXxh~DrKk~m(G4smAYzFw zT}cCLT? some View { + VStack { + Text("우리 팀의 궁극적인 목표") + .pretendardFont(family: .semiBold, size: 16) + .foregroundStyle(.gray60) + + + Image(asset: .TeamiIntroduce) + .resizable() + .scaledToFit() + .frame(width: 56, height: 56) + + Spacer() + .frame(height: 10) + + HStack { + + Spacer() + + TypingText( + text: "안녕하세요 1조입니다! 👋", + font: .pretendardFontFamily(family: .bold, size: 16), + perChar: 0.06, + startDelay: 0.15, + showsCursor: false + ) + + Spacer() + + } + + } + .padding(16) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.staticWhite) + .shadow(color: .shadowColor, radius: 2) + ) + .padding(.horizontal, 16) + + } + + @ViewBuilder + fileprivate func teamIntroduceList() -> some View { + HStack { + Text("우리 팀만의 특징") + .pretendardFont(family: .regular, size: 16) + .foregroundStyle(.basicBlack) + + Spacer() + } + .padding(16) + + } + + + @ViewBuilder + private func introduceList() -> some View { + VStack { + introduceItem( + image: .TeamInfroduce_Person, + title: "다양성 존중", + subtitle: "각자의 강점과 개성을 인정하고 서로 보완하며 성장합니다." + ) + + introduceItem( + image: .TeamInfroduce_Accident, + title: "창의적 사고", + subtitle: "새로운 아이디어를 자유롭게 제안하고 실험하는 문화를 추구 합니다." + ) + + introduceItem( + image: .TeamInfroduce_Heart, + title: "따뜻한 소통", + subtitle: "솔직하고 건설적인 피드백으로 서로를 도우며 성장합니다." + ) + + introduceItem( + image: .TeamInfroduce_Circle, + title: "목표지향", + subtitle: "명확한 목표를 설정하고 함께 달성해나가는 팀워크를 발휘합니다." + ) + } + } + + @ViewBuilder + fileprivate func introduceItem( + image: ImageAsset, + title: String, + subtitle: String + ) -> some View { + VStack { + HStack { + Image(asset: image) + .resizable() + .scaledToFit() + .frame(width: 35, height: 35) + + Spacer() + .frame(width: 10) + + VStack(alignment: .leading) { + Text(title) + .pretendardFont(family: .regular, size: 12) + .foregroundStyle(.textSecondary100) + + Spacer() + .frame(height: 4) + + Text(subtitle) + .pretendardFont(family: .regular, size: 14) + .foregroundStyle(.textPrimary) + + } + + Spacer() + } + .padding(16) + } + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.staticWhite) + .shadow(color: .shadowColor, radius: 2) + ) + .padding(.horizontal, 16) + } +} + + +#Preview { + TeamIntroduceView() +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TypingText.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TypingText.swift new file mode 100644 index 0000000..646fae4 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TypingText.swift @@ -0,0 +1,62 @@ +// +// TypingText.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import SwiftUI + +struct TypingText: View { + let text: String + var font: Font = .pretendardFontFamily(family: .semiBold, size: 20) + var perChar: Double = 0.05 // 글자당 지연(초) + var startDelay: Double = 0.0 // 시작 지연(초) + var showsCursor: Bool = true // 커서 표시 여부 + + @Environment(\.accessibilityReduceMotion) private var reduceMotion + @State private var displayed: String = "" + @State private var blink = false + @State private var task: Task? + + var body: some View { + HStack(spacing: 0) { + Text(displayed).font(font) + + if showsCursor { + Text("|") + .font(font) + .opacity(blink ? 0 : 1) + .animation(.easeInOut(duration: 0.6).repeatForever(), value: blink) + .accessibilityHidden(true) + } + } + .onAppear { startTyping() } + .onChange(of: text) { _ , _ in startTyping() } + .onDisappear { task?.cancel() } + } + + private func startTyping() { + task?.cancel() + + guard !reduceMotion else { + displayed = text + blink.toggle() + return + } + + displayed = "" + blink = false + + task = Task { + if startDelay > 0 { + try? await Task.sleep(nanoseconds: UInt64(startDelay * 1_000_000_000)) + } + for ch in text { + displayed.append(ch) + try? await Task.sleep(nanoseconds: UInt64(perChar * 1_000_000_000)) + } + await MainActor.run { blink = true } + } + } +} From e4f4c2bdc2cc8ffb7aa760bf927a0de32082560f Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 19:17:17 +0900 Subject: [PATCH 13/20] =?UTF-8?q?=F0=9F=AA=9B[chore]:=20=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TeamInfroduce_Accident.imageset/Contents.json | 0 .../TeamInfroduce_Accident.png | Bin .../TeamInfroduce_Circle.imageset/Contents.json | 0 .../TeamInfroduce_Circle.png | Bin .../TeamInfroduce_Heart.imageset/Contents.json | 0 .../TeamInfroduce_Heart.png | Bin .../TeamInfroduce_Person.imageset/Contents.json | 0 .../TeamInfroduce_Person.png | Bin 8 files changed, 0 insertions(+), 0 deletions(-) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Accident.imageset/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Circle.imageset/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Heart.imageset/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Person.imageset/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png (100%) diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png From b2fbc1982d3a13bd5049b557345ea46d0ebb5153 Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 19:15:13 +0900 Subject: [PATCH 14/20] =?UTF-8?q?=E2=9C=A8[feat]:=20=ED=8C=80=EC=86=8C?= =?UTF-8?q?=EA=B0=9C=20=20=EB=B7=B0=EA=B5=AC=ED=98=84=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resources/Assets.xcassets/Contents.json | 3 + .../TeamiIntroduce.imageset/Contents.json | 21 +++ .../TeamiIntroduce.png | Bin 0 -> 5072 bytes .../Contents.json | 21 +++ .../TeamInfroduce_Accident.png | Bin 0 -> 2379 bytes .../Contents.json | 21 +++ .../TeamInfroduce_Circle.png | Bin 0 -> 2714 bytes .../Contents.json | 21 +++ .../TeamInfroduce_Heart.png | Bin 0 -> 2500 bytes .../Contents.json | 21 +++ .../TeamInfroduce_Person.png | Bin 0 -> 2619 bytes .../DesignSytstem/Image/ImageAsset.swift | 5 + .../View/IntorduceCoordinatorView.swift | 3 +- .../TeamIntroduce/TeamIntroduceView.swift | 178 ++++++++++++++++++ .../TeamIntroduce/TypingText.swift | 62 ++++++ 15 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/TeamiIntroduce.png create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/Contents.json create mode 100644 TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TypingText.swift diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json index 73c0059..7f73912 100644 --- a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json @@ -2,5 +2,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "compression-type" : "automatic" } } diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/Contents.json new file mode 100644 index 0000000..98b4695 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "TeamiIntroduce.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/TeamiIntroduce.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/TeamiIntroduce.png new file mode 100644 index 0000000000000000000000000000000000000000..fffc4cba806aa2df087a6cf692224ba50b3f080b GIT binary patch literal 5072 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 literal 0 HcmV?d00001 diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json new file mode 100644 index 0000000..a67eb54 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "TeamInfroduce_Circle.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png new file mode 100644 index 0000000000000000000000000000000000000000..3cb6fcf2ff289912017dfe2888ab002dab9787cb GIT binary patch literal 2714 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 U&;|acqW}N^07*qoM6N<$g5pvL0{{R3 literal 0 HcmV?d00001 diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json new file mode 100644 index 0000000..c0f5096 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "TeamInfroduce_Heart.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png new file mode 100644 index 0000000000000000000000000000000000000000..600341c98648a040d052bcd4038b3be3de9ac7ac GIT binary patch literal 2500 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 O0000500009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yPgLg`M~ze|@c866HscT>}*u1GkvUvF5qlPVMhB0wS@yKkM4iGv7o0Pm!@YN?ilRRE&U^nAb<>`f#RRF)6~6M40r6@V&{M*mr}WnEC-~&zh8$yrXLSEdvn>WBFVB)2BPY0wX3{FV1I%nOF|-cVIdMhC>rZ`i&MJDtiM| z7`3V|D&$fJ>lDubRoYb6l&D5Cp{k_L!I1KKJ*81x2i(2^*Dh(kc{VuP@|K#F6L z*a(^E^jhWd(1?xkD*+MJC2Y`KF1I`24fdnt3L+sTVM<3YAOpg&qkBV@UIv%T%@;H^ zH$oCcD98=@UQ}neoy^Y8_If70>qTjya|tS11Y@z(!qux+VfVA&1fSmr zJuklkuO9gc_=5pBbnrzu`{xfpgZuB^y#tYW9A5wR>u~(|acDq5C=D!iT*jS%d(vw` z{yhwbP*fegH#s)76J3ybqI$M`J3D_#qXoTQ1IBghxXvCsN1(OU!PmL*xLO=7kS{10 zrRSj9WP<2i6rSqX4ii&T@ZiA^*qfUn8czT%-2E#-`CN?6?hY+WkBL_1>y+>8k-v7op*kR#1;6*aGF`>M2AuLG}QR)zXYu&O96lMoP z(TXk=`Z&voy4o_iMajFplR<72hi zx=1`4eL>VnD~5P>G^s=yc5L4U+jn$8-|bs)@W3Gm`KMtn=!e6-J@B8;u0v;MC+yg^ z6}+hBLx&E-o4@@Hw6(QDZ_i=C%*_ZbAF>6mV8~{&`-NHmER84Q3I;8rCLda%QKoW9 zWxw{HOqA^a>{6FB#}xC(sI+gTKI}%Y*S`28R#4UO)JKe(+3t;q>X#pwa5! zqYux)hv&}2zVAK{f!Pqe_VZuC&6_tdFrGvWTS|FuQCWhKe`aF*7O(b`@pOK2!bM$N zJJZzb@zMZ#8M~g{y_-unJUmR%h-oB|rQv9r&V`dSgkvs}rjb~NuXVZId~IjvGyFZ0 zDDeB1_I8TNm9iSe=MPf1%Uje8)5@fqCgLf2;=~EFnw!4Bqk39frR7d9B6^Li1!J`V z%r(_$6FZ^5uMdKuBu3vH7TRhMquds-EM-|AYR!7EU>2{dtApW34uXRu!BOXAomMMzFa>l|YkeJQ^I`RRjPr99z52 zgF3wq%^}9q3u;73XVRo?vqSBpcu~GwcQ8Ht(7C~AFcd97m=8~MnL41NG+FRTDCZuI ztg;_bGMOxCZL@n2NFyxM9Te>emktaJw6;I7ITA~zSmmmy?J6xcWJ_|)vN=bf^BSZS zD&CZA5lHyPjT>#H?aE5Dcpr?(P>YS+$iuIk4JM;O;r=ds?Urr9MazW}|0P;)jLG zZEOAtfq-`7{le6kXFcP>+72jJJ}Y4<7E;XfSom*fYM2cK0|%^D>up%&R|v8qy&`In zxg8E$t+l$R+0i&yTUS%`8m1?OySl>N9q(+8|2~6FN4>>TXxh~DrKk~m(G4smAYzFw zT}cCLT? some View { + VStack { + Text("우리 팀의 궁극적인 목표") + .pretendardFont(family: .semiBold, size: 16) + .foregroundStyle(.gray60) + + + Image(asset: .TeamiIntroduce) + .resizable() + .scaledToFit() + .frame(width: 56, height: 56) + + Spacer() + .frame(height: 10) + + HStack { + + Spacer() + + TypingText( + text: "안녕하세요 1조입니다! 👋", + font: .pretendardFontFamily(family: .bold, size: 16), + perChar: 0.06, + startDelay: 0.15, + showsCursor: false + ) + + Spacer() + + } + + } + .padding(16) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.staticWhite) + .shadow(color: .shadowColor, radius: 2) + ) + .padding(.horizontal, 16) + + } + + @ViewBuilder + fileprivate func teamIntroduceList() -> some View { + HStack { + Text("우리 팀만의 특징") + .pretendardFont(family: .regular, size: 16) + .foregroundStyle(.basicBlack) + + Spacer() + } + .padding(16) + + } + + + @ViewBuilder + private func introduceList() -> some View { + VStack { + introduceItem( + image: .TeamInfroduce_Person, + title: "다양성 존중", + subtitle: "각자의 강점과 개성을 인정하고 서로 보완하며 성장합니다." + ) + + introduceItem( + image: .TeamInfroduce_Accident, + title: "창의적 사고", + subtitle: "새로운 아이디어를 자유롭게 제안하고 실험하는 문화를 추구 합니다." + ) + + introduceItem( + image: .TeamInfroduce_Heart, + title: "따뜻한 소통", + subtitle: "솔직하고 건설적인 피드백으로 서로를 도우며 성장합니다." + ) + + introduceItem( + image: .TeamInfroduce_Circle, + title: "목표지향", + subtitle: "명확한 목표를 설정하고 함께 달성해나가는 팀워크를 발휘합니다." + ) + } + } + + @ViewBuilder + fileprivate func introduceItem( + image: ImageAsset, + title: String, + subtitle: String + ) -> some View { + VStack { + HStack { + Image(asset: image) + .resizable() + .scaledToFit() + .frame(width: 35, height: 35) + + Spacer() + .frame(width: 10) + + VStack(alignment: .leading) { + Text(title) + .pretendardFont(family: .regular, size: 12) + .foregroundStyle(.textSecondary100) + + Spacer() + .frame(height: 4) + + Text(subtitle) + .pretendardFont(family: .regular, size: 14) + .foregroundStyle(.textPrimary) + + } + + Spacer() + } + .padding(16) + } + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.staticWhite) + .shadow(color: .shadowColor, radius: 2) + ) + .padding(.horizontal, 16) + } +} + + +#Preview { + TeamIntroduceView() +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TypingText.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TypingText.swift new file mode 100644 index 0000000..646fae4 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TypingText.swift @@ -0,0 +1,62 @@ +// +// TypingText.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import SwiftUI + +struct TypingText: View { + let text: String + var font: Font = .pretendardFontFamily(family: .semiBold, size: 20) + var perChar: Double = 0.05 // 글자당 지연(초) + var startDelay: Double = 0.0 // 시작 지연(초) + var showsCursor: Bool = true // 커서 표시 여부 + + @Environment(\.accessibilityReduceMotion) private var reduceMotion + @State private var displayed: String = "" + @State private var blink = false + @State private var task: Task? + + var body: some View { + HStack(spacing: 0) { + Text(displayed).font(font) + + if showsCursor { + Text("|") + .font(font) + .opacity(blink ? 0 : 1) + .animation(.easeInOut(duration: 0.6).repeatForever(), value: blink) + .accessibilityHidden(true) + } + } + .onAppear { startTyping() } + .onChange(of: text) { _ , _ in startTyping() } + .onDisappear { task?.cancel() } + } + + private func startTyping() { + task?.cancel() + + guard !reduceMotion else { + displayed = text + blink.toggle() + return + } + + displayed = "" + blink = false + + task = Task { + if startDelay > 0 { + try? await Task.sleep(nanoseconds: UInt64(startDelay * 1_000_000_000)) + } + for ch in text { + displayed.append(ch) + try? await Task.sleep(nanoseconds: UInt64(perChar * 1_000_000_000)) + } + await MainActor.run { blink = true } + } + } +} From 17281267cd7619e3c97bc26b815661c18ff087b3 Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 19:17:17 +0900 Subject: [PATCH 15/20] =?UTF-8?q?=F0=9F=AA=9B[chore]:=20=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=9D=B4=EB=8F=99=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TeamInfroduce_Accident.imageset/Contents.json | 0 .../TeamInfroduce_Accident.png | Bin .../TeamInfroduce_Circle.imageset/Contents.json | 0 .../TeamInfroduce_Circle.png | Bin .../TeamInfroduce_Heart.imageset/Contents.json | 0 .../TeamInfroduce_Heart.png | Bin .../TeamInfroduce_Person.imageset/Contents.json | 0 .../TeamInfroduce_Person.png | Bin 8 files changed, 0 insertions(+), 0 deletions(-) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Accident.imageset/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Circle.imageset/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Heart.imageset/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Person.imageset/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/{ => Images}/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png (100%) diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png From 5e08da395306d1599d6a1b178cb6e059bfb2762a Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 19:58:57 +0900 Subject: [PATCH 16/20] =?UTF-8?q?=E2=9C=A8[feat]:=20=20=EB=B7=B0=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EA=B0=88=EC=9D=84=EB=95=8C=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=20=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presnetaion/TeamBlog/TeamBlogView.swift | 108 +++++++-------- .../TeamBlog/TeamBlogViewModel.swift | 2 +- .../TeamIntroduce/TeamIntroduceView.swift | 127 +++++++++--------- 3 files changed, 111 insertions(+), 126 deletions(-) diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift index 5e2b6fa..23773a8 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift @@ -7,10 +7,14 @@ import SwiftUI +import SwiftUI + struct TeamBlogView: View { @EnvironmentObject var coordinator: IntroduceCoordinator @Bindable var viewModel: TeamBlogViewModel + // 현재까지 보여줄 수 있는 최대 인덱스 + @State private var currentMaxIndex: Int = -1 init(viewModel: TeamBlogViewModel) { self.viewModel = viewModel @@ -22,32 +26,26 @@ struct TeamBlogView: View { .edgesIgnoringSafeArea(.all) VStack { - Spacer() - .frame(height: 14) + Spacer().frame(height: 14) CustomNavigationBackBar(text: "팀블로그") { coordinator.goBack() } - Spacer() - .frame(height: 20) + Spacer().frame(height: 20) blogHeaderView() - Spacer() - .frame(height: 10) + Spacer().frame(height: 10) blogList() - Spacer() blogHintBanner() - Spacer() - .frame(height: 30) + Spacer().frame(height: 30) } - } .onAppear { viewModel.send(.onAppear) @@ -60,9 +58,7 @@ extension TeamBlogView { @ViewBuilder private func blogHeaderView() -> some View { VStack(alignment: .center) { - Spacer() - .frame(height: 16) - + Spacer().frame(height: 16) Circle() .fill(.gray40) @@ -72,32 +68,25 @@ extension TeamBlogView { .resizable() .scaledToFit() .frame(width: 30, height: 30) - } - Spacer() - .frame(height: 10) + Spacer().frame(height: 10) HStack { - Spacer() - Text("팀원들의 블로그") .pretendardFont(family: .regular, size: 13) .foregroundStyle(.staticBlack) - Spacer() } - Spacer() - .frame(height: 10) + Spacer().frame(height: 10) Text("각자 공부한 내용및 경험을 공유 하는 공간입니다.") .pretendardFont(family: .regular, size: 13) .foregroundStyle(.blueGray) - Spacer() - .frame(height: 16) + Spacer().frame(height: 16) } .background( @@ -106,41 +95,48 @@ extension TeamBlogView { .shadow(color: .shadowColor, radius: 2) ) .padding(.horizontal, 16) - } - - + // 💡 순차 애니메이션 blog 리스트 @ViewBuilder private func blogList() -> some View { if !viewModel.isLoading { - VStack { - blogListitem( - name: "김민희", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://0minnie0.tistory.com/", - action: { item in - viewModel.send(.presentWebView(url: item)) - } - ) - - blogListitem( - name: "서원지", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://velog.io/@suhwj/posts", - action: { item in - viewModel.send(.presentWebView(url: item)) + let blogs = [ + (name: "김민희", + blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", + blogLink: "https://0minnie0.tistory.com/"), + (name: "서원지", + blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", + blogLink: "https://velog.io/@suhwj/posts"), + (name: "홍석현", + blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", + blogLink: "https://velog.io/@gustjrghd/posts") + ] + + VStack(spacing: 12) { + ForEach(blogs.indices, id: \.self) { index in + let blog = blogs[index] + + blogListitem( + name: blog.name, + blogTitle: blog.blogTitle, + blogLink: blog.blogLink + ) { link in + viewModel.send(.presentWebView(url: link)) } - ) - - blogListitem( - name: "홍석현", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://velog.io/@gustjrghd/posts", - action: { item in - viewModel.send(.presentWebView(url: item)) + // 👉 순차 애니메이션 + .opacity(index <= currentMaxIndex ? 1 : 0) + .offset(y: index <= currentMaxIndex ? 0 : 20) + .onAppear { + guard index > currentMaxIndex else { return } + let delay = 0.6 + 0.5 * Double(index) // 각 카드 간 간격 늘림 + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + withAnimation(.spring(response: 0.8, dampingFraction: 0.85)) { + currentMaxIndex = index + } + } } - ) + } } } else { ForEach(0..<3, id: \.self) { _ in @@ -154,16 +150,13 @@ extension TeamBlogView { name: String, blogTitle: String, blogLink: String, - action: @escaping (String) -> Void + action: @escaping (String) -> Void ) -> some View { VStack { HStack { Circle() .fill(.gray.opacity(0.3)) .frame(width: 40, height: 40) - .overlay { - - } VStack(alignment: .leading) { HStack { @@ -195,10 +188,8 @@ extension TeamBlogView { } Spacer() - } .padding(16) - } .background( RoundedRectangle(cornerRadius: 12) @@ -222,16 +213,13 @@ extension TeamBlogView { } .padding(.vertical, 16) } - .background( RoundedRectangle(cornerRadius: 12) .fill(.staticWhite) .shadow(color: .shadowColor, radius: 2) ) .padding(.horizontal, 16) - } - } #Preview { diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift index 1886836..c11c6db 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift @@ -56,7 +56,7 @@ final class TeamBlogViewModel { private func fetchIntroductions() async { isLoading = true Task { - try await Task.sleep(for: .seconds(1)) + try await Task.sleep(for: .seconds(0.4)) isLoading = false } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift index ae05238..638632f 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift @@ -7,42 +7,53 @@ import SwiftUI +struct IntroduceItem: Identifiable { + let id = UUID() + let image: ImageAsset + let title: String + let subtitle: String +} + struct TeamIntroduceView: View { @EnvironmentObject var coordinator: IntroduceCoordinator - var body: some View { - ZStack { - Color.staticWhite - .edgesIgnoringSafeArea(.all) - - VStack { - Spacer() - .frame(height: 14) - - CustomNavigationBackBar(text: "팀소개") { - coordinator.goBack() - } + // 현재까지 보여줄 수 있는 최대 인덱스 + @State private var currentMaxIndex: Int = -1 - Spacer() - .frame(height: 20) + // 소개 아이템 배열 + private let introduceItems: [IntroduceItem] = [ + .init(image: .TeamInfroduce_Person, title: "다양성 존중", subtitle: "각자의 강점과 개성을 인정하고 서로 보완하며 성장합니다."), + .init(image: .TeamInfroduce_Accident, title: "창의적 사고", subtitle: "새로운 아이디어를 자유롭게 제안하고 실험하는 문화를 추구 합니다."), + .init(image: .TeamInfroduce_Heart, title: "따뜻한 소통", subtitle: "솔직하고 건설적인 피드백으로 서로를 도우며 성장합니다."), + .init(image: .TeamInfroduce_Circle, title: "목표지향", subtitle: "명확한 목표를 설정하고 함께 달성해나가는 팀워크를 발휘합니다.") + ] - teamIntorduceHeader() + var body: some View { + ZStack { + Color.staticWhite + .edgesIgnoringSafeArea(.all) - teamIntroduceList() + VStack { + Spacer().frame(height: 14) + CustomNavigationBackBar(text: "팀소개") { + coordinator.goBack() + } - introduceList() + Spacer().frame(height: 20) - Spacer() + teamIntorduceHeader() + teamIntroduceList() + introduceList() - } + Spacer() } } + } } extension TeamIntroduceView { - - + // 타이틀 박스 @ViewBuilder private func teamIntorduceHeader() -> some View { VStack { @@ -50,19 +61,15 @@ extension TeamIntroduceView { .pretendardFont(family: .semiBold, size: 16) .foregroundStyle(.gray60) - Image(asset: .TeamiIntroduce) .resizable() .scaledToFit() .frame(width: 56, height: 56) - Spacer() - .frame(height: 10) + Spacer().frame(height: 10) HStack { - Spacer() - TypingText( text: "안녕하세요 1조입니다! 👋", font: .pretendardFontFamily(family: .bold, size: 16), @@ -70,11 +77,8 @@ extension TeamIntroduceView { startDelay: 0.15, showsCursor: false ) - Spacer() - } - } .padding(16) .background( @@ -83,52 +87,51 @@ extension TeamIntroduceView { .shadow(color: .shadowColor, radius: 2) ) .padding(.horizontal, 16) - } + // 팀 특징 타이틀 @ViewBuilder fileprivate func teamIntroduceList() -> some View { HStack { Text("우리 팀만의 특징") .pretendardFont(family: .regular, size: 16) .foregroundStyle(.basicBlack) - Spacer() } .padding(16) - } - + // 애니메이션되며 등장하는 소개 리스트 @ViewBuilder private func introduceList() -> some View { - VStack { - introduceItem( - image: .TeamInfroduce_Person, - title: "다양성 존중", - subtitle: "각자의 강점과 개성을 인정하고 서로 보완하며 성장합니다." - ) - - introduceItem( - image: .TeamInfroduce_Accident, - title: "창의적 사고", - subtitle: "새로운 아이디어를 자유롭게 제안하고 실험하는 문화를 추구 합니다." - ) - - introduceItem( - image: .TeamInfroduce_Heart, - title: "따뜻한 소통", - subtitle: "솔직하고 건설적인 피드백으로 서로를 도우며 성장합니다." - ) - - introduceItem( - image: .TeamInfroduce_Circle, - title: "목표지향", - subtitle: "명확한 목표를 설정하고 함께 달성해나가는 팀워크를 발휘합니다." - ) + let indices = Array(introduceItems.indices) + + VStack(spacing: 12) { + ForEach(indices, id: \.self) { index in + let item = introduceItems[index] + + introduceItem( + image: item.image, + title: item.title, + subtitle: item.subtitle + ) + .opacity(index <= currentMaxIndex ? 1 : 0) + .offset(y: index <= currentMaxIndex ? 0 : 12) + .onAppear { + // 이미 등장한 인덱스는 무시 + guard index > currentMaxIndex else { return } + let delay = 0.25 + 0.15 * Double(index) + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + withAnimation(.spring(response: 0.5, dampingFraction: 0.85)) { + currentMaxIndex = index + } + } + } + } } } + // 개별 아이템 뷰 @ViewBuilder fileprivate func introduceItem( image: ImageAsset, @@ -142,21 +145,16 @@ extension TeamIntroduceView { .scaledToFit() .frame(width: 35, height: 35) - Spacer() - .frame(width: 10) + Spacer().frame(width: 10) VStack(alignment: .leading) { Text(title) .pretendardFont(family: .regular, size: 12) .foregroundStyle(.textSecondary100) - - Spacer() - .frame(height: 4) - + Spacer().frame(height: 4) Text(subtitle) .pretendardFont(family: .regular, size: 14) .foregroundStyle(.textPrimary) - } Spacer() @@ -172,7 +170,6 @@ extension TeamIntroduceView { } } - #Preview { TeamIntroduceView() } From 36cd5486c6c30552d62363b238929070908edd20 Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 20:01:56 +0900 Subject: [PATCH 17/20] =?UTF-8?q?=E2=9C=A8[feat]:=20=20=EC=95=A0=EB=8B=88?= =?UTF-8?q?=EB=A9=94=EC=9D=B4=EC=85=98=20=EA=B0=84=EA=B2=A9=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Presnetaion/TeamBlog/TeamBlogView.swift | 11 +++++------ .../Presnetaion/TeamBlog/TeamBlogViewModel.swift | 3 ++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift index 23773a8..5248c71 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift @@ -14,7 +14,6 @@ struct TeamBlogView: View { @Bindable var viewModel: TeamBlogViewModel // 현재까지 보여줄 수 있는 최대 인덱스 - @State private var currentMaxIndex: Int = -1 init(viewModel: TeamBlogViewModel) { self.viewModel = viewModel @@ -125,14 +124,14 @@ extension TeamBlogView { viewModel.send(.presentWebView(url: link)) } // 👉 순차 애니메이션 - .opacity(index <= currentMaxIndex ? 1 : 0) - .offset(y: index <= currentMaxIndex ? 0 : 20) + .opacity(index <= viewModel.currentMaxIndex ? 1 : 0) + .offset(y: index <= viewModel.currentMaxIndex ? 0 : 20) .onAppear { - guard index > currentMaxIndex else { return } - let delay = 0.6 + 0.5 * Double(index) // 각 카드 간 간격 늘림 + guard index > viewModel.currentMaxIndex else { return } + let delay = 0.5 + 0.2 * Double(index) // 각 카드 간 간격 늘림 DispatchQueue.main.asyncAfter(deadline: .now() + delay) { withAnimation(.spring(response: 0.8, dampingFraction: 0.85)) { - currentMaxIndex = index + viewModel.currentMaxIndex = index } } } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift index c11c6db..c909c14 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift @@ -12,6 +12,7 @@ import Combine @Observable final class TeamBlogViewModel { private(set) var isLoading = false + var currentMaxIndex: Int = -1 private let route: (IntroduceCoordinator.Action) -> Void @@ -56,7 +57,7 @@ final class TeamBlogViewModel { private func fetchIntroductions() async { isLoading = true Task { - try await Task.sleep(for: .seconds(0.4)) + try await Task.sleep(for: .seconds(0.3)) isLoading = false } From 28c972589983c277ecdfba2e1b5028814e6ca8a2 Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 20:20:44 +0900 Subject: [PATCH 18/20] =?UTF-8?q?=F0=9F=AA=9B[chore]:=20=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presnetaion/TeamBlog/BlogItem.swift | 15 ++++++ .../Presnetaion/TeamBlog/TeamBlogView.swift | 53 ++++++++----------- .../TeamBlog/TeamBlogViewModel.swift | 11 ++++ .../TeamIntroduce/IntroduceItem.swift | 15 ++++++ .../TeamIntroduce/TeamIntroduceView.swift | 6 --- 5 files changed, 62 insertions(+), 38 deletions(-) create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/BlogItem.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/IntroduceItem.swift diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/BlogItem.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/BlogItem.swift new file mode 100644 index 0000000..49247a9 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/BlogItem.swift @@ -0,0 +1,15 @@ +// +// BlogItem.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import Foundation + +struct BlogItem: Identifiable { + let id = UUID() + let name: String + let blogTitle: String + let blogLink: String +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift index 5248c71..122ced5 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift @@ -100,41 +100,30 @@ extension TeamBlogView { @ViewBuilder private func blogList() -> some View { if !viewModel.isLoading { - let blogs = [ - (name: "김민희", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://0minnie0.tistory.com/"), - (name: "서원지", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://velog.io/@suhwj/posts"), - (name: "홍석현", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://velog.io/@gustjrghd/posts") - ] + VStack(spacing: 12) { - ForEach(blogs.indices, id: \.self) { index in - let blog = blogs[index] - - blogListitem( - name: blog.name, - blogTitle: blog.blogTitle, - blogLink: blog.blogLink - ) { link in - viewModel.send(.presentWebView(url: link)) - } - // 👉 순차 애니메이션 - .opacity(index <= viewModel.currentMaxIndex ? 1 : 0) - .offset(y: index <= viewModel.currentMaxIndex ? 0 : 20) - .onAppear { - guard index > viewModel.currentMaxIndex else { return } - let delay = 0.5 + 0.2 * Double(index) // 각 카드 간 간격 늘림 - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - withAnimation(.spring(response: 0.8, dampingFraction: 0.85)) { - viewModel.currentMaxIndex = index - } + ForEach(Array(viewModel.blogs.indices), id: \.self) { index in + let blog = viewModel.blogs[index] + + blogListitem( + name: blog.name, + blogTitle: blog.blogTitle, + blogLink: blog.blogLink + ) { link in + viewModel.send(.presentWebView(url: link)) + } + .opacity(index <= viewModel.currentMaxIndex ? 1 : 0) + .offset(y: index <= viewModel.currentMaxIndex ? 0 : 20) + .onAppear { + guard index > viewModel.currentMaxIndex else { return } + let delay = 0.25 + 0.12 * Double(index) // ⏱ 첫 대기, 카드 간 텀 조정 + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + withAnimation(.spring(response: 0.8, dampingFraction: 0.85)) { + viewModel.currentMaxIndex = index + } + } } - } } } } else { diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift index c909c14..094c6c1 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift @@ -15,6 +15,17 @@ final class TeamBlogViewModel { var currentMaxIndex: Int = -1 private let route: (IntroduceCoordinator.Action) -> Void + let blogs: [BlogItem] = [ + .init(name: "김민희", + blogTitle: "모바일개발과 크로스플랫폼 기술을 공유합니다", + blogLink: "https://0minnie0.tistory.com/"), + .init(name: "서원지", + blogTitle: "모바일개발과 크로스플랫폼 기술을 공유합니다", + blogLink: "https://velog.io/@suhwj/posts"), + .init(name: "홍석현", + blogTitle: "모바일개발과 크로스플랫폼 기술을 공유합니다", + blogLink: "https://velog.io/@gustjrghd/posts") + ] diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/IntroduceItem.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/IntroduceItem.swift new file mode 100644 index 0000000..7503d88 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/IntroduceItem.swift @@ -0,0 +1,15 @@ +// +// IntroduceItem.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import Foundation + +struct IntroduceItem: Identifiable { + let id = UUID() + let image: ImageAsset + let title: String + let subtitle: String +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift index 638632f..5f5d434 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift @@ -7,12 +7,6 @@ import SwiftUI -struct IntroduceItem: Identifiable { - let id = UUID() - let image: ImageAsset - let title: String - let subtitle: String -} struct TeamIntroduceView: View { @EnvironmentObject var coordinator: IntroduceCoordinator From 255d5940aa0556fbdbf37683226d5a9de79ced3b Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 12 Aug 2025 20:20:44 +0900 Subject: [PATCH 19/20] =?UTF-8?q?=F0=9F=AA=9B[chore]:=20=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B6=84=EB=A6=AC=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presnetaion/TeamBlog/BlogItem.swift | 15 ++++++ .../Presnetaion/TeamBlog/TeamBlogView.swift | 53 ++++++++----------- .../TeamBlog/TeamBlogViewModel.swift | 11 ++++ .../TeamIntroduce/IntroduceItem.swift | 15 ++++++ .../TeamIntroduce/TeamIntroduceView.swift | 6 --- 5 files changed, 62 insertions(+), 38 deletions(-) create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/BlogItem.swift create mode 100644 TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/IntroduceItem.swift diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/BlogItem.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/BlogItem.swift new file mode 100644 index 0000000..49247a9 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/BlogItem.swift @@ -0,0 +1,15 @@ +// +// BlogItem.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import Foundation + +struct BlogItem: Identifiable { + let id = UUID() + let name: String + let blogTitle: String + let blogLink: String +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift index 5248c71..122ced5 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift @@ -100,41 +100,30 @@ extension TeamBlogView { @ViewBuilder private func blogList() -> some View { if !viewModel.isLoading { - let blogs = [ - (name: "김민희", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://0minnie0.tistory.com/"), - (name: "서원지", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://velog.io/@suhwj/posts"), - (name: "홍석현", - blogTitle: "모바일개발과크로스플랫폼기술을공유합니다", - blogLink: "https://velog.io/@gustjrghd/posts") - ] + VStack(spacing: 12) { - ForEach(blogs.indices, id: \.self) { index in - let blog = blogs[index] - - blogListitem( - name: blog.name, - blogTitle: blog.blogTitle, - blogLink: blog.blogLink - ) { link in - viewModel.send(.presentWebView(url: link)) - } - // 👉 순차 애니메이션 - .opacity(index <= viewModel.currentMaxIndex ? 1 : 0) - .offset(y: index <= viewModel.currentMaxIndex ? 0 : 20) - .onAppear { - guard index > viewModel.currentMaxIndex else { return } - let delay = 0.5 + 0.2 * Double(index) // 각 카드 간 간격 늘림 - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - withAnimation(.spring(response: 0.8, dampingFraction: 0.85)) { - viewModel.currentMaxIndex = index - } + ForEach(Array(viewModel.blogs.indices), id: \.self) { index in + let blog = viewModel.blogs[index] + + blogListitem( + name: blog.name, + blogTitle: blog.blogTitle, + blogLink: blog.blogLink + ) { link in + viewModel.send(.presentWebView(url: link)) + } + .opacity(index <= viewModel.currentMaxIndex ? 1 : 0) + .offset(y: index <= viewModel.currentMaxIndex ? 0 : 20) + .onAppear { + guard index > viewModel.currentMaxIndex else { return } + let delay = 0.25 + 0.12 * Double(index) // ⏱ 첫 대기, 카드 간 텀 조정 + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + withAnimation(.spring(response: 0.8, dampingFraction: 0.85)) { + viewModel.currentMaxIndex = index + } + } } - } } } } else { diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift index c909c14..094c6c1 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift @@ -15,6 +15,17 @@ final class TeamBlogViewModel { var currentMaxIndex: Int = -1 private let route: (IntroduceCoordinator.Action) -> Void + let blogs: [BlogItem] = [ + .init(name: "김민희", + blogTitle: "모바일개발과 크로스플랫폼 기술을 공유합니다", + blogLink: "https://0minnie0.tistory.com/"), + .init(name: "서원지", + blogTitle: "모바일개발과 크로스플랫폼 기술을 공유합니다", + blogLink: "https://velog.io/@suhwj/posts"), + .init(name: "홍석현", + blogTitle: "모바일개발과 크로스플랫폼 기술을 공유합니다", + blogLink: "https://velog.io/@gustjrghd/posts") + ] diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/IntroduceItem.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/IntroduceItem.swift new file mode 100644 index 0000000..7503d88 --- /dev/null +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/IntroduceItem.swift @@ -0,0 +1,15 @@ +// +// IntroduceItem.swift +// TeamIntroduce +// +// Created by Wonji Suh on 8/12/25. +// + +import Foundation + +struct IntroduceItem: Identifiable { + let id = UUID() + let image: ImageAsset + let title: String + let subtitle: String +} diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift index 638632f..5f5d434 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift @@ -7,12 +7,6 @@ import SwiftUI -struct IntroduceItem: Identifiable { - let id = UUID() - let image: ImageAsset - let title: String - let subtitle: String -} struct TeamIntroduceView: View { @EnvironmentObject var coordinator: IntroduceCoordinator From 051d4ebb13206154c4d6737a3d718912f31b4f06 Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 13 Aug 2025 09:57:19 +0900 Subject: [PATCH 20/20] =?UTF-8?q?=F0=9F=AA=9B[chore]:=20=20=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=20=ED=8C=8C=EC=9D=BC=20=20=EC=BD=94=EB=94=94=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contents.json | 0 .../TeamInfroduce_Accident.png | Bin .../Contents.json | 0 .../TeamInfroduce_Circle.png | Bin .../Contents.json | 0 .../TeamInfroduce_Heart.png | Bin .../Contents.json | 0 .../TeamInfroduce_Person.png | Bin .../DesignSytstem/Image/ImageAsset.swift | 18 ++++---- .../View/IntorduceCoordinatorView.swift | 6 +-- .../IntroductionRow/IntroductionRowView.swift | 4 +- .../View/Components/MissionView.swift | 2 +- .../View/Components/TeamExploreItem.swift | 6 +-- .../View/Components/TeamExploreRowView.swift | 2 +- .../Sources/Presnetaion/Root/RootView.swift | 4 +- .../Presnetaion/TeamBlog/TeamBlogView.swift | 5 +-- .../TeamBlog/TeamBlogViewModel.swift | 39 +++++++++++------- .../TeamIntroduce/TeamIntroduceView.swift | 15 +++---- .../Sources/Presnetaion/WebView/WebView.swift | 12 ++++-- 19 files changed, 63 insertions(+), 50 deletions(-) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/{TeamInfroduce_Accident.imageset => teamInfroduceAccident.imageset}/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/{TeamInfroduce_Accident.imageset => teamInfroduceAccident.imageset}/TeamInfroduce_Accident.png (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/{TeamInfroduce_Circle.imageset => teamInfroduceCircle.imageset}/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/{TeamInfroduce_Circle.imageset => teamInfroduceCircle.imageset}/TeamInfroduce_Circle.png (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/{TeamInfroduce_Heart.imageset => teamInfroduceHeart.imageset}/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/{TeamInfroduce_Heart.imageset => teamInfroduceHeart.imageset}/TeamInfroduce_Heart.png (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/{TeamInfroduce_Person.imageset => teamInfroducePerson.imageset}/Contents.json (100%) rename TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/{TeamInfroduce_Person.imageset => teamInfroducePerson.imageset}/TeamInfroduce_Person.png (100%) diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceAccident.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceAccident.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceAccident.imageset/TeamInfroduce_Accident.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Accident.imageset/TeamInfroduce_Accident.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceAccident.imageset/TeamInfroduce_Accident.png diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceCircle.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceCircle.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceCircle.imageset/TeamInfroduce_Circle.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Circle.imageset/TeamInfroduce_Circle.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceCircle.imageset/TeamInfroduce_Circle.png diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceHeart.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceHeart.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceHeart.imageset/TeamInfroduce_Heart.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Heart.imageset/TeamInfroduce_Heart.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceHeart.imageset/TeamInfroduce_Heart.png diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroducePerson.imageset/Contents.json similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/Contents.json rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroducePerson.imageset/Contents.json diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroducePerson.imageset/TeamInfroduce_Person.png similarity index 100% rename from TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamInfroduce_Person.imageset/TeamInfroduce_Person.png rename to TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroducePerson.imageset/TeamInfroduce_Person.png diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift index 0c7754c..628858e 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift @@ -15,12 +15,14 @@ enum ImageAsset: String { case leftArrow case glabal case link - case TeamAgreementLogo - case TeamBlogLogo - case TeamIntroductionLogo - case TeamiIntroduce - case TeamInfroduce_Accident - case TeamInfroduce_Heart - case TeamInfroduce_Circle - case TeamInfroduce_Person + case arrowRight + case missionLogo + case teamAgreementLogo + case teamBlogLogo + case teamIntroductionLogo + case teamiIntroduce + case teamInfroduceAccident + case teamInfroduceHeart + case teamInfroduceCircle + case teamInfroducePerson } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift index 65e7877..eae6272 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift @@ -9,7 +9,7 @@ import SwiftUI import SwiftData struct IntorduceCoordinatorView : View { - @EnvironmentObject private var coordinator: IntroduceCoordinator + @StateObject private var coordinator = IntroduceCoordinator() var sharedModelContainer: ModelContainer = { let schema = Schema([ Item.self, @@ -42,14 +42,14 @@ extension IntorduceCoordinatorView { case .teamAgreement: ContentView() case .teamIntroduce: - TeamIntroduceView() + TeamIntroduceView(coordinator: coordinator) .navigationBarBackButtonHidden() case .teamBlog: TeamBlogView(viewModel: .init(coordinator: coordinator)) .navigationBarBackButtonHidden() case .webView(let url): - WebView(url: url) + WebView(coordinator: coordinator, url: url) } } } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift index 00c6862..b13c9b1 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift @@ -40,11 +40,11 @@ struct IntroductionRowView: View { Spacer() - Image("ArrowRight") + Image(asset: .arrowRight) .foregroundStyle(Color(hex: "717182")) } .padding(16) - .background(Color.staticWhite) + .background(.staticWhite) .clipShape(RoundedRectangle(cornerRadius: 12)) .shadow(radius: 1) } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MissionView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MissionView.swift index 81bbc47..1564e9c 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MissionView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MissionView.swift @@ -10,7 +10,7 @@ import SwiftUI struct MissionView: View { var body: some View { VStack(alignment: .center, spacing: 12) { - Image("MissionLogo") + Image(asset: .missionLogo) .renderingMode(.original) Text("우리의 미션") diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreItem.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreItem.swift index 53531c3..e8dd8b1 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreItem.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreItem.swift @@ -40,11 +40,11 @@ enum TeamExploreItem: CaseIterable, Identifiable { var imageName: ImageAsset { switch self { case .introduction: - return .TeamIntroductionLogo + return .teamIntroductionLogo case .agreement: - return .TeamAgreementLogo + return .teamAgreementLogo case .blog: - return .TeamBlogLogo + return .teamBlogLogo } } } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift index 1bce1c2..0586493 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift @@ -33,7 +33,7 @@ struct TeamExploreRowView: View { Spacer() - Image("ArrowRight") + Image(asset: .arrowRight) .foregroundStyle(Color(hex: "717182")) } .padding(16) diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift index eaad56c..51db37e 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift @@ -8,10 +8,10 @@ import SwiftUI struct RootView: View { - @StateObject var coordinator = IntroduceCoordinator() +// @StateObject var coordinator = IntroduceCoordinator() var body: some View { IntorduceCoordinatorView() - .environmentObject(coordinator) +// .environmentObject(coordinator) } } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift index 122ced5..ca13ab7 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift @@ -10,11 +10,8 @@ import SwiftUI import SwiftUI struct TeamBlogView: View { - @EnvironmentObject var coordinator: IntroduceCoordinator @Bindable var viewModel: TeamBlogViewModel - // 현재까지 보여줄 수 있는 최대 인덱스 - init(viewModel: TeamBlogViewModel) { self.viewModel = viewModel } @@ -28,7 +25,7 @@ struct TeamBlogView: View { Spacer().frame(height: 14) CustomNavigationBackBar(text: "팀블로그") { - coordinator.goBack() + viewModel.send(.backToRoot) } Spacer().frame(height: 20) diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift index 094c6c1..a4db07d 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift @@ -8,13 +8,19 @@ import SwiftUI import Combine +import SwiftUI +import Observation + @MainActor @Observable final class TeamBlogViewModel { private(set) var isLoading = false var currentMaxIndex: Int = -1 + // 라우팅 private let route: (IntroduceCoordinator.Action) -> Void + private let goBack: () -> Void + let blogs: [BlogItem] = [ .init(name: "김민희", blogTitle: "모바일개발과 크로스플랫폼 기술을 공유합니다", @@ -27,30 +33,33 @@ final class TeamBlogViewModel { blogLink: "https://velog.io/@gustjrghd/posts") ] - - // MARK: - Init - /// 기본값: no-op(코디네이터 주입 안 해도 안전) - init(route: @escaping (IntroduceCoordinator.Action) -> Void = { _ in }) { + init( + route: @escaping (IntroduceCoordinator.Action) -> Void = { _ in }, + goBack: @escaping () -> Void = {} + ) { self.route = route + self.goBack = goBack } - /// 편의 이니셜라이저: 코디네이터 주입 + // 코디네이터 주입 편의 생성자 convenience init(coordinator: IntroduceCoordinator?) { if let coordinator { - self.init(route: { [weak coordinator] action in coordinator?.send(action) }) + self.init( + route: { [weak coordinator] action in coordinator?.send(action) }, + goBack: { [weak coordinator] in coordinator?.goBack() } + ) } else { - self.init() // no-op + self.init() } } - - // MARK: - Action (뷰에서 이 enum만 쓰면 됩니다) + // MARK: - Action enum Action { case onAppear case refresh - case presentWebView(url: String) + case backToRoot } // MARK: - Single entrypoint @@ -61,16 +70,16 @@ final class TeamBlogViewModel { case .presentWebView(let url): route(.present(.webView(url: url))) + + case .backToRoot: + goBack() } } // MARK: - Private private func fetchIntroductions() async { isLoading = true - Task { - try await Task.sleep(for: .seconds(0.3)) - - isLoading = false - } + defer { isLoading = false } + try? await Task.sleep(for: .seconds(0.3)) } } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift index 5f5d434..00b2c79 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift @@ -9,17 +9,17 @@ import SwiftUI struct TeamIntroduceView: View { - @EnvironmentObject var coordinator: IntroduceCoordinator + @ObservedObject var coordinator: IntroduceCoordinator // 현재까지 보여줄 수 있는 최대 인덱스 @State private var currentMaxIndex: Int = -1 // 소개 아이템 배열 private let introduceItems: [IntroduceItem] = [ - .init(image: .TeamInfroduce_Person, title: "다양성 존중", subtitle: "각자의 강점과 개성을 인정하고 서로 보완하며 성장합니다."), - .init(image: .TeamInfroduce_Accident, title: "창의적 사고", subtitle: "새로운 아이디어를 자유롭게 제안하고 실험하는 문화를 추구 합니다."), - .init(image: .TeamInfroduce_Heart, title: "따뜻한 소통", subtitle: "솔직하고 건설적인 피드백으로 서로를 도우며 성장합니다."), - .init(image: .TeamInfroduce_Circle, title: "목표지향", subtitle: "명확한 목표를 설정하고 함께 달성해나가는 팀워크를 발휘합니다.") + .init(image: .teamInfroducePerson, title: "다양성 존중", subtitle: "각자의 강점과 개성을 인정하고 서로 보완하며 성장합니다."), + .init(image: .teamInfroduceAccident, title: "창의적 사고", subtitle: "새로운 아이디어를 자유롭게 제안하고 실험하는 문화를 추구 합니다."), + .init(image: .teamInfroduceHeart, title: "따뜻한 소통", subtitle: "솔직하고 건설적인 피드백으로 서로를 도우며 성장합니다."), + .init(image: .teamInfroduceCircle, title: "목표지향", subtitle: "명확한 목표를 설정하고 함께 달성해나가는 팀워크를 발휘합니다.") ] var body: some View { @@ -55,7 +55,7 @@ extension TeamIntroduceView { .pretendardFont(family: .semiBold, size: 16) .foregroundStyle(.gray60) - Image(asset: .TeamiIntroduce) + Image(asset: .teamiIntroduce) .resizable() .scaledToFit() .frame(width: 56, height: 56) @@ -165,5 +165,6 @@ extension TeamIntroduceView { } #Preview { - TeamIntroduceView() + @Previewable @StateObject var coordinator: IntroduceCoordinator = IntroduceCoordinator() + TeamIntroduceView(coordinator: coordinator) } diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift index 86027ee..7c4180b 100644 --- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift +++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift @@ -9,12 +9,16 @@ import SwiftUI struct WebView: View { - @EnvironmentObject var coordinator: IntroduceCoordinator + @ObservedObject var coordinator: IntroduceCoordinator var url: String - init(url: String) { - self.url = url - } + init( + coordinator: IntroduceCoordinator, + url: String + ) { + self._coordinator = ObservedObject(wrappedValue: coordinator) + self.url = url + } var body: some View { ZStack {