Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// IntroduceCoordinator.swift
// TeamIntroduce
//
// Created by Wonji Suh on 8/11/25.
//

import Combine
import SwiftUI

Comment on lines +8 to +10
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요건 중복되어 있어서 제거되는게 좋을 것 같아요 ~!


final class IntroduceCoordinator: NavigationControlling, ObservableObject {

@Published var path = NavigationPath()

// 액션 기반 네비게이션
enum Action {
case start
case pop
case popToRoot
case present(_ route: IntroduceRoute)
case replaceStack(_ routes: [IntroduceRoute])
}

func send(_ action: Action) {
switch action {
case .start:
start()

case .pop:
if !path.isEmpty { path.removeLast() }

case .popToRoot:
path = .init()


case .present(let route):
path.append(route)

case .replaceStack(let routes):
replaceStack(routes)

}
}

// MARK: - NavigationControlling 요구 구현
func start() {
reset()
send(.present(.introduceMain))
}

// ⚠️ 프로토콜이 요구한다면 private 붙이면 안 됨
func reset() {
path = .init()
}

// 스택 교체
func replaceStack(_ routes: [IntroduceRoute], animated: Bool = true) {
let apply = {
self.path = .init()
routes.forEach { self.path.append($0) }
}

if animated {
withAnimation(.default) { apply() }
} else {
apply()
}
}

// 편의 오버로드
func replaceStack(_ route: IntroduceRoute, animated: Bool = true) {
replaceStack([route], animated: animated)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// IntroduceRoute.swift
// TeamIntroduce
//
// Created by Wonji Suh on 8/11/25.
//

import Foundation

enum IntroduceRoute: Hashable {

// 소개 페이지 메인 화면
case introduceMain
// 팀약속
case teamAgreement
// 팀소개
case teamIntroduce
// 팀 블로그
case teamBlog

}
Original file line number Diff line number Diff line change
@@ -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 .teamBlog:
EmptyView()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(.present(.teamAgreement))
}
}
}

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)
}
Original file line number Diff line number Diff line change
@@ -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()
}