1. 프로젝트 개설

 

프로젝트명 : NavigationLinkTest 

 

Models.swift 파일 생성

 

 

 

 

 

 

2. Models.swift 파일 작성

 

데이터셋 + 데이터 선언을 합니다.

 

import Foundation

struct MemberModel: Identifiable {
    var id: Int
    var name: String
    var age: Int
    
    init(id: Int, name: String, age: Int) {
        self.id = id
        self.name = name
        self.age = age
    }
}


let members: [MemberModel] = [ MemberModel(id: 1, name: "앤토니", age: 21), MemberModel(id: 2, name: "스티븐", age: 29), MemberModel(id: 0, name: "제임스", age: 23)]

 

 

 

 

 

3. CententView.swift 파일 작성 : 간단하게 List로 구현 예시

 

세부 정보를 보여주는 화면을 그리기 위한 DetailView와, 목록 정보를 보여주는 화면을 그리기 위한 ContentView 작성

 

import SwiftUI


struct ContentView: View {
    var body: some View {
        NavigationStack {
            List(members) {member in
                NavigationLink(member.name) {
                    DetailView(person: member)
                }
            }
            .navigationBarTitle("명단")
        }
    }
}

struct DetailView: View {
    var person: MemberModel
    
    var body: some View {
        VStack {
            Image(systemName: "person.circle")
                .resizable()
                .frame(width: 100, height: 100, alignment: .center)
            Text(person.name)
                .font(.system(size: 60))
            Text("\(person.age)세")
                .font(.system(size: 40))
        }
        .navigationTitle("상세내역")
        // NavigationBar에 보이는 Title의 크기를 작게 보이게 설정!
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 

NavigationStack을 아래와 같이 작성해도 동일하게 작동합니다. A안은 위 코드의 일부이고, B안과 같이 작성해도 동일하게 정상 작동합니다.

 

// A안

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List(members) {member in
                NavigationLink(member.name) {
                    DetailView(person: member)
                }
            }
            .navigationBarTitle("명단")
        }
    }
}
// B안

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List(members) {member in
                NavigationLink(destination: DetailView(person: member), label: {
                    Text(member.name)
                })
            }
            .navigationBarTitle("명단")
        }
    }
}

 

 

 

 

 

4. api call로 return받은 data를 활용해 목록 출력 및 버튼 누르면 각 항목에 대한 상세 화면 이동 기능 구현 예시 : api 콜 부분과 모델 세팅

 

// ViewModels.swift


class ClassTransactionVM: ObservableObject {
    @Published var classTransactionHistoryData: ClassTransactionM?
    
    // check Optional
    private var urlOptional: URL?
    
    init(api_url: String, class_id: Int) {
        guard let components = URLComponents(string: "\(api_url)/v2/transaction/class/\(class_id)") else {
            print("Invalid URL")
            return
        }
        
        guard let url = components.url else {
            print("Failed to create URL")
            return
        }
        
        urlOptional = url
    }
    public func getData(completion: @escaping (ClassTransactionM?) -> Void) {
        if let targetUrl = urlOptional {
            var request = URLRequest(url: targetUrl)
            request.httpMethod = "GET"
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
            URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
                guard data == data else {
                    print("Error: \(error!)")
                    return
                }
                
                guard let data = data else {
                    print("No data received")
                    return
                }
                
//                // 데이터 확인
//                guard let jsonString = String(data: data, encoding: .utf8) else {
//                    print("Error!")
//                    return
//                }
//                print(jsonString)

                do {
                    let responseDecoded = try JSONDecoder().decode(ClassTransactionM.self, from: data)
                    DispatchQueue.main.async {
                        self.classTransactionHistoryData = responseDecoded
                    }
                } catch {
                    print("\n\n[Error Decoding Response] ClassTransactionVM\n\n\(error.localizedDescription)\n\n\(error)")
                }
            })
            .resume()
        } else {
            print("if let urlOptional 관련 올")
        }
    }
}

 

// Models.swift


// MARK: - ClassTransaction
struct ClassTransactionM: Codable {
    let result: ResultOfClassTransaction
    let status: StatusOfApiM
}
    
struct ResultOfClassTransaction: Codable {
    let currentPage, maxPage, totalData: Int?
    let classTransactionData: [ClassTransactionData]
}

// MARK: - ClassTransactionDatum
struct ClassTransactionData: Codable {
    let transactionId, transactionMoney: Int
    let transactionDate, category, detail: String?
    let deposit: Bool?
    let manager, student: User

    enum CodingKeys: String, CodingKey {
        case transactionId, transactionDate, category, detail, transactionMoney, deposit, manager, student
    }
}

struct User: Codable {
    let studentId, number, jobId: Int?
    let studentName, jobTitle: String?
}

 

 

 

 

 

5. api call로 return받은 data를 활용해 목록 출력 및 버튼 누르면 각 항목에 대한 상세 화면 이동 기능 구현 예시 : View부분

 

struct ClassTransactionListView: View {
    @State private var isActive: Bool = false
    @State private var selectedTransactionId: Int = 0
    
    @AppStorage("classIdStored") var classIdStored: Int = 0
    
    @ObservedObject var classTransactionM: ClassTransactionVM
    
    init(api_url: String, classIdStored: Int) {
        self.classTransactionM = ClassTransactionVM(api_url: api_url, class_id: classIdStored)
        self.loadClassTransactionData()
    }
    
    func loadClassTransactionData() {
        classTransactionM.getData(completion: {data in
            DispatchQueue.main.async {
                self.classTransactionM.classTransactionHistoryData = data
            }
        })
    }
    
    var body: some View {
        if let classTransactionHistory = classTransactionM.classTransactionHistoryData {
            if classTransactionHistory.result.totalData == 0 {
                Text("거래 내역이 없어요")
                    .font(.custom(pretendard_bold, size: 20))
                    .foregroundColor(Color.gray600)
                    .padding(.top, 50)
            } else {
                NavigationLink(destination: ClassTransactionDetail(transactionId:selectedTransactionId), isActive: $isActive, label: { EmptyView() } )
                ForEach(classTransactionHistory.result.classTransactionData, id: \.transactionId) { eachClassTransactionData in
                    // ZStack 시작
                    ZStack {
                        Rectangle()
                            .foregroundColor(Color.white)
                            .frame(height: 100)
                        // 안의 ZStack 시작
                        ZStack {
                            // 날짜, 카테고리
                            HStack {
                                VStack(alignment: .leading) {
                                    Text(eachClassTransactionData.transactionDate ?? "")
                                        .foregroundColor(Color.gray600)
                                        .font(.custom(pretendard_bold, size: 14))
                                        .padding(.bottom, 5)
                                        .padding(.leading, 50)
                                    
                                    Text(eachClassTransactionData.category ?? "")
                                        .foregroundColor(Color.black)
                                        .font(.custom(pretendard_bold, size: 16))
                                        .padding(.leading, 50)
                                }
                                Spacer()
                            }
                        }
                        // 직업
                        ZStack {
                            HStack {
                                Spacer()
                                Text(eachClassTransactionData.manager.jobTitle ?? "")
                                    .foregroundColor(Color.black)
                                    .font(.custom(pretendard_bold, size: 16))
                                    .padding(.trailing, 40)
                                Spacer()
                            }
                        }
                        // 입,출금 여부
                        if let isDeposit = eachClassTransactionData.deposit {
                            ZStack {
                                if isDeposit {
                                    HStack {
                                        Spacer()
                                        ZStack {
                                            RoundedRectangle(cornerRadius: 5)
                                                .foregroundColor(.red100)
                                                .frame(width: 45, height: 23)
                                                .padding(.trailing, 120)
                                            Text("입금")
                                                .font(.custom(pretendard_bold, size: 15))
                                                .foregroundColor(.red300)
                                                .padding(.trailing, 120)
                                        }
                                    }
                                } else {
                                    HStack {
                                        Spacer()
                                        ZStack {
                                            RoundedRectangle(cornerRadius: 5)
                                                .foregroundColor(.blue100)
                                                .frame(width: 45, height: 23)
                                                .padding(.trailing, 120)
                                            Text("출금")
                                                .font(.custom(pretendard_bold, size: 15))
                                                .foregroundColor(.blue300)
                                                .padding(.trailing, 120)
                                        }
                                    }
                                }
                            }
                            // 가격
                            ZStack {
                                HStack {
                                    Spacer()
                                    if isDeposit {
                                        ZStack {
                                            if let transactionMoney = eachClassTransactionData.transactionMoney {
                                                Text(String(transactionMoney))
                                                    .font(.custom(pretendard_bold, size: 15))
                                                    .foregroundColor(.red300)
                                                    .padding(.trailing, 55)
                                            } else {
                                                Text("Loading...")
                                                    .foregroundColor(Color.gray600)
                                            }
                                        }
                                    } else {
                                        if let transactionMoney = eachClassTransactionData.transactionMoney {
                                            Text(String(transactionMoney))
                                                .font(.custom(pretendard_bold, size: 15))
                                                .foregroundColor(.blue300)
                                                .padding(.trailing, 55)
                                        } else {
                                            Text("Loading...")
                                                .foregroundColor(Color.gray600)
                                        }
                                    }
                                }
        //                        Spacer()
                            }
                        } else {
                            Text("Loading...")
                                .foregroundColor(Color.gray600)
                        }
                        // 안의 ZStack 종료
                    }
                    .onTapGesture {
                        isActive = true
                        selectedTransactionId = eachClassTransactionData.transactionId
                    }
                    // ZStack 종료
                }
                .onAppear {
                    DispatchQueue.main.async {
                        self.loadClassTransactionData()
                    }
                }
            }
        } else {
            LoadingView()
        }
    }
}




struct ClassTransactionDetail: View {
    private var transaction_id: Int
    @State private var showTransactionDetails: Bool = false
    @State private var classTransactionDetail: ClassTransactionDetailVM
    
    init (transactionId: Int) {
        self.transaction_id = transactionId
        self.classTransactionDetail = ClassTransactionDetailVM(api_url: api_url, transaction_id: transaction_id)
    }
    
    var body: some View {
        VStack {
            Text("상세 내역")
                .font(.custom(pretendard_bold, size: 20))
                .foregroundColor(Color.black)
                .onAppear {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                        showTransactionDetails = true
                    }
                }
            Spacer()
            if showTransactionDetails {
                if let classTransactionDetailInfo = classTransactionDetail.classTransactionDetail {
                    Text(classTransactionDetailInfo.result.category ?? "")
                        .font(.custom(pretendard_regular, size: 20))
                        .foregroundColor(Color.black)
                        .padding(5)
                    if let classTransactionCurrency = classTransactionDetailInfo.result.currency {
                        // 지급, 입금
                        if classTransactionDetailInfo.result.plus {
                            Text("+\(classTransactionDetailInfo.result.transactionMoney)\(classTransactionCurrency)")
                                    .font(.custom(pretendard_bold, size: 30))
                                    .foregroundColor(Color.black)
                                    .padding()
                                    .padding()
                        // 이체, 출금
                        } else {
                            Text("-\(classTransactionDetailInfo.result.transactionMoney)\(classTransactionCurrency)")
                                .font(.custom(pretendard_bold, size: 30))
                                .foregroundColor(Color.black)
                                .padding()
                                .padding()
                        }
                    } else {
                        Text("Loading...")
                    }
                    ZStack {
                        HStack {
                            Text("내용")
                                .font(.custom(pretendard_regular, size: 17))
                                .foregroundColor(Color.black)
                                .padding()
                            Spacer()
                        }
                        HStack {
                            Spacer()
                            Text(classTransactionDetailInfo.result.detail ?? "")
                                .font(.custom(pretendard_bold, size: 17))
                                .foregroundColor(Color.black)
                                .padding()
                        }
                    }
                    .padding(5)
                    ZStack {
                        HStack {
                            Text("일시")
                                .font(.custom(pretendard_regular, size: 17))
                                .foregroundColor(Color.black)
                                .padding()
                            Spacer()
                        }
                        HStack {
                            Spacer()
                            Text(classTransactionDetailInfo.result.transactionDate ?? "")
                                .font(.custom(pretendard_bold, size: 17))
                                .foregroundColor(Color.black)
                                .padding()
                        }
                    }
                    .padding(5)
                    ZStack {
                        HStack {
                            Text("거래 후 잔액")
                                .font(.custom(pretendard_regular, size: 17))
                                .foregroundColor(Color.black)
                                .padding()
                            Spacer()
                        }
                        HStack {
                            Spacer()
                            Text("\(classTransactionDetailInfo.result.leftMoney)")
                                .font(.custom(pretendard_bold, size: 17))
                                .foregroundColor(Color.black)
                                .padding()
                        }
                    }
                    .padding(5)
                    Spacer()
                    ZStack {
                        HStack {
                            Text("작업자 이름")
                                .font(.custom(pretendard_regular, size: 17))
                                .foregroundColor(Color.black)
                                .padding()
                            Spacer()
                        }
                        HStack {
                            Spacer()
                            Text(classTransactionDetailInfo.result.managerInfo.studentName ?? "")
                                .font(.custom(pretendard_bold, size: 17))
                                .foregroundColor(Color.black)
                                .padding()
                        }
                    }
                    .padding(5)
                    ZStack {
                        HStack {
                            Text("작업자 직업")
                                .font(.custom(pretendard_regular, size: 17))
                                .foregroundColor(Color.black)
                                .padding()
                            Spacer()
                        }
                        HStack {
                            Spacer()
                            Text(classTransactionDetailInfo.result.managerInfo.studentJob ?? "")
                                .font(.custom(pretendard_bold, size: 17))
                                .foregroundColor(Color.black)
                                .padding()
                        }
                    }
                    .padding(5)
                    ZStack {
                        HStack {
                            Text("작업자 출석 번호")
                                .font(.custom(pretendard_regular, size: 17))
                                .foregroundColor(Color.black)
                                .padding()
                            Spacer()
                        }
                        HStack {
                            Spacer()
                            if let workerNumber = classTransactionDetailInfo.result.managerInfo.studentNumber {
                                // 비정상=투자일때
                                if workerNumber == -1 {
                                    Text("-")
                                        .font(.custom(pretendard_bold, size: 17))
                                        .foregroundColor(Color.black)
                                        .padding()
                                // 정상일때
                                } else {
                                    Text("\(workerNumber)")
                                        .font(.custom(pretendard_bold, size: 17))
                                        .foregroundColor(Color.black)
                                        .padding()
                                }
                            }
                        }
                    }
                    .padding(5)
                } else {
                    Text("Loading...")
                        .foregroundColor(Color.gray600)
                }
                Spacer()
            }
        }
    }
}

 

LoadingView가 궁금하다면 내용을 아래 글에서 확인할 수 있습니다.

https://growingsaja.tistory.com/832

 

[SwiftUI] 로딩바 출력하는 기능 예제

1. LoadingView 구조체 예시 struct LoadingView: View { @State private var isLoading = false var body: some View { ZStack { // 로딩 바를 나타낼 배경 Color.gray.opacity(0.3) .ignoresSafeArea() .opacity(isLoading ? 1 : 0) // 로딩 중에만 보

growingsaja.tistory.com

 

 

 

 

 

6. 결과

 

 

 

 

+ Recent posts