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
6. 결과
'Development > iOS' 카테고리의 다른 글
[SwiftUI] Api 연결 소스코드 예시 - Method : POST (0) | 2023.05.21 |
---|---|
[SwiftUI] Api 통신 연결 소스코드 예시 - Method : GET (0) | 2023.05.16 |
[SwiftUI] 로딩바 출력하는 기능 예제 (0) | 2023.04.28 |
[iOS] Apple Developer Program 멤버십 갱신하기 (0) | 2023.04.27 |
[SwiftUI] 버튼을 누르면, 일정 시간 동안만 노출이 되고 사라지는 문구+도형 기능 구현하기 (0) | 2023.04.27 |