< 작업 환경 >

 

 

 

 

 

 

1. Model 파일

 


import Foundation

// 학생의 클래스 join을 위한 post api에서 사용
struct TryJoinClassM: Codable {
    let status: StatusOfApiM
    let result: ResultOfJoinClassM?
}

struct StatusOfApiM: Codable {
    let status, message: String
}

struct ResultOfJoinClassM: Codable {
    let studentId: Int
}

 

 

 - 데이터 형태 예시 (참고)

{
  "status": {
    "status": "E000",
    "message": "Success"
  },
  "result": {
    "studentId": 93
  }
}

 

 

 

 

 

2. Control or ViewModel 파일

 



import Foundation

// 클래스 합류 시도
class TryJoinClassVM: ObservableObject {
    @Published var tryJoinClass: TryJoinClassM?
    // test
    private var test_url: URL?
    private var test_data: Data?
    
    init(api_url: String, parameters: [String: Any]) {
        guard let url = URL(string: "\(api_url)/v2/student/join-class") else {
            print("Invalid URL")
            return
        }
        test_url = url
        guard let postData = try? JSONSerialization.data(withJSONObject: parameters, options: []) else {
            print("Failed to serialize parameters")
            return
        }
        
        // test
        test_data = postData
    }
    public func getData(completion: @escaping (TryJoinClassM?) -> Void) {
        if let target_url = test_url {
            var request = URLRequest(url: target_url)
            request.httpMethod = "POST"
            if let target_data = test_data {
                request.httpBody = target_data
            }
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
            URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
                guard error == nil 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(TryJoinClassM.self, from: data)
                    DispatchQueue.main.async {
                        self.tryJoinClass = responseDecoded
                        completion(self.tryJoinClass)
                    }
                } catch {
                    print("\n\n[Error Decoding Response] TryJoinClassVM\n\n\(error.localizedDescription)\n\n\(error)")
                }
            })
            .resume()
        }
    }
}

 

 

 

 

 

3. View 파일

 

import SwiftUI

struct TryJoinClassModalView: View {
    
    // 모달 컨트롤
    @Environment(\.presentationMode) var presentation
    
    // 학급 합류 성공시 Main 들어갔을때 토스트 발생시키기 위한 변수
    @AppStorage("isJoinClassNow") var isJoinClassNow : Bool = false
    @AppStorage("isInClass") var isInClass : Bool = false
    @AppStorage("classIdStored") var classIdStored: Int = 0
    @AppStorage("userIdStored") var userIdStored : Int = 0
    @AppStorage("studentIdStored") var studentIdStored: Int = 0
    
    // 토스트 메시지
    @State private var toast: FancyToast? = nil
    
    // 초대 코드 입력받을 공간 만들어두기
    @State private var inviteCode: String = ""
    
    // 초대 코드 입력 전 상태 여부
    @State private var isBeforeInputClassCode: Bool = true
    
    // 초대 코드 정상 처리된 class id 정보 저장
    @State private var classIdTryJoin: Int = 0
    
    // 출석번호 저장
    @State private var studentAttendanceNumberInput: String = ""
    
    // 말풍선 안내
    @State private var isShowingDescription = false
    
    var body: some View {
        VStack {
            // 초대 코드 입력 전
            if isBeforeInputClassCode {
                Spacer()
                Text("우리 반 초대코드 입력하기")
                    .font(.custom(pretendard_bold, size: 20))
                    .padding(.bottom, 50)
                // 말풍선 시작
                HStack {
                    Button(action: {
                        // 물음표 아이콘을 누를 때마다 설명 화면 표시 여부를 토글
                        withAnimation(.easeInOut) {
                            isShowingDescription.toggle()
                        }
                        // 2초 후에 설명 화면 숨김
                        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                            withAnimation(.easeInOut) {
                                isShowingDescription = false
                            }
                        }
                    }, label: {
                        HStack {
                            Text("초대코드는 무엇인가요?")
                                .font(.custom(pretendard_bold, size: 16))
                                .foregroundColor(Color.gray)
                            Image(systemName: "questionmark.circle")
                                .resizable()
                                .frame(width: 16, height: 16)
                                .foregroundColor(Color.gray)
                        }
                    })
                    .padding()
                    .overlay(
                        // 설명 화면
                        Group {
                            if isShowingDescription {
                                VStack(spacing: 0) {
                                    ZStack {
                                        // 뭉툭한 네모 모양의 테두리
                                        RoundedRectangle(cornerRadius: 10)
                                            .fill(Color.orange1)
                                            .frame(width: 390, height:80)
                                            .transition(.opacity)
                                        // 설명 내용
                                        Text("선생님께서 알려주신 코드를 입력해요. 우리반 초대 코드를 모르겠다면 선생님께 질문해봐요.")
                                            .padding()
                                            .multilineTextAlignment(.center)
                                            .transition(.opacity)
                                            .font(.custom(pretendard_bold, size: 16))
                                            .foregroundColor(Color.orange2)
                                    }
                                    TriangleView()
                                        .foregroundColor(Color.orange1)
                                        .frame(width: 18, height: 12)
                                    Rectangle()
                                        .frame(width: 150, height: 120)
                                        .foregroundColor(Color.white.opacity(0))
                                }
                            }
                        }
                    )
                }
                .padding(.top, 50)
                // 말풍선 끝
                TextField("초대코드 입력", text: $inviteCode)
                    .cornerRadius(8)
                    .padding()
                    .frame(width: 300)
                    .font(.custom(pretendard_bold, size: 16))
                    .background(Color.orange1)
                Button {
                    if inviteCode != "" {
                        print("콜 시작")
                        @ObservedObject var classCodeCheck = ClassCodeVM(api_url: api_url, tryFindClassCode: inviteCode)
                        classCodeCheck.getData(completion: {data in
                            print("if let 진입")
                            if let classJoinInfo = classCodeCheck.findClass {
                                print("존재하는 학급인거 확인 완료")
                                if classJoinInfo.status.status == "E000" {
                                    print("classJoinInfo E000 확인")
                                    if let resultOfFindClass = classJoinInfo.result {
                                        print("classJoinInfo.result if let 성공")
                                        // 클래스 참가 신청 api VM 콜해야합니다!!! ToDoList
                                        self.isBeforeInputClassCode = false
                                        self.classIdTryJoin = resultOfFindClass.classId
                                    }
                                } else {
                                    print("if let 실패")
                                    toast = FancyToast(type: .warning, title: "잘못된 초대코드", message: "존재하지 않는 초대코드이거나 초대코드에 오타가 있어요")
                                }
                            }
                        })
                    } else {
                        toast = FancyToast(type: .warning, title: "초대코드가 비어있어요", message: "초대코드를 입력해주세요")
                    }
                } label: {
                    VStack {
                        // 초대 버튼 활성화
                        if inviteCode != "" {
                            ZStack {
                                RoundedRectangle(cornerRadius: 12)
                                    .foregroundColor(Color.orange1)
                                    .frame(width: 200, height: 50)
                                Text("우리반 가입하기")
                                    .foregroundColor(Color.orange3)
                                    .font(.custom(pretendard_bold, size: 16))
                            }
                        // 초대 버튼 비활성화
                        } else {
                            ZStack {
                                RoundedRectangle(cornerRadius: 12)
                                    .foregroundColor(Color.gray1)
                                    .frame(width: 200, height: 50)
                                Text("우리반 가입하기")
                                    .foregroundColor(Color.gray3)
                                    .font(.custom(pretendard_bold, size: 16))
                            }
                        }
                    }
                }
                // 초대코드로 클래스 찾기 종료
                Spacer()
                // 나가기 버튼
                Button(action: {
                    presentation.wrappedValue.dismiss()
                }) {
                    ZStack {
                        RoundedRectangle(cornerRadius: 12)
                            .foregroundColor(Color.gray150)
                            .frame(width: 125, height: 50)
                        Text("뒤로가기")
                            .foregroundColor(Color.black)
                            .font(.custom(pretendard_bold, size: 16))
                    }
                }
            // 초대 코드 입력 후 출석번호 입력
            } else {
                Spacer()
                Text("내 출석번호 입력하기")
                    .font(.custom(pretendard_bold, size: 20))
                    .padding(.bottom, 20)
                Text("출석번호를 모르겠다면 선생님께 여쭤보세요")
                    .font(.custom(pretendard_bold, size: 16))
                    .foregroundColor(Color.gray)
                    .padding(.bottom, 50)
                TextField("내 출석번호를 입력해요", text: $studentAttendanceNumberInput)
                    .cornerRadius(8)
                    .padding()
                    .frame(width: 300)
                    .font(.custom(pretendard_bold, size: 16))
                    .background(Color.orange1)
                Button {
                    if studentAttendanceNumberInput != "" {
                        @ObservedObject var joinClass = TryJoinClassVM(api_url: api_url, parameters: ["userId": userIdStored, "classId": classIdTryJoin, "studentNumber": studentAttendanceNumberInput])
                        joinClass.getData(completion: {data in
                            if let tryJoinClassButton = joinClass.tryJoinClass {
                                // 학급 합류 성공
                                if tryJoinClassButton.status.status == "E000" {
                                    // 학생 아이디 저장
                                    if let studentInfo = tryJoinClassButton.result {
                                        self.studentIdStored = studentInfo.studentId
                                    }
                                    // 이외 클래스 진입 상태로 변경
                                    self.isInClass = true
                                    self.classIdStored = classIdTryJoin
                                    self.isJoinClassNow = true
                                    print("학급 가입 성공")
                                    presentation.wrappedValue.dismiss()
                                } else if tryJoinClassButton.status.status == "E601" {
                                    print("이미 학급에 합류되어있음")
                                    toast = FancyToast(type: .error, title: "이미 학급에 합류해있어요", message: "내가 속한 학급 목록에서 우리 반을 찾아 들어가보세요.")
                                } else if tryJoinClassButton.status.status == "E606" {
                                    print("이미 다른 친구 사용 출석번호")
                                    toast = FancyToast(type: .error, title: "본인의 출석번호를 입력했나요?", message: "이미 다른 친구가 등록한 출석번호예요. 내 출석번호로 다른 친구가 가입했다면 선생님께 가서 이야기해봐요.")
                                } else {
                                    print("시스템 에러")
                                    toast = FancyToast(type: .error, title: "시스템 오류", message: "개발자에게 문의해주세요.")
                                }
                            } else {
                                print("tryJoinClassButton if let 실패")
                            }
                        })
                    } else {
                        toast = FancyToast(type: .warning, title: "출석 번호를 입력해봐요", message: "본인의 출석 번호를 입력해보세요. 모르겠다면 선생님께 여쭤봐도 좋아요.")
                    }
                } label: {
                    VStack {
                        // 학급 Join 버튼 활성화
                        if studentAttendanceNumberInput != "" {
                            ZStack {
                                RoundedRectangle(cornerRadius: 12)
                                    .foregroundColor(Color.orange1)
                                    .frame(width: 200, height: 50)
                                Text("우리반 가입하기")
                                    .foregroundColor(Color.orange3)
                                    .font(.custom(pretendard_bold, size: 16))
                            }
                        // 초대 버튼 비활성화
                        } else {
                            ZStack {
                                RoundedRectangle(cornerRadius: 12)
                                    .foregroundColor(Color.gray1)
                                    .frame(width: 200, height: 50)
                                Text("우리반 가입하기")
                                    .foregroundColor(Color.gray3)
                                    .font(.custom(pretendard_bold, size: 16))
                            }
                        }
                    }
                }
                // 초대코드로 클래스 찾기 종료
                Spacer()
                // 나가기 버튼
                Button(action: {
                    presentation.wrappedValue.dismiss()
                }) {
                    ZStack {
                        RoundedRectangle(cornerRadius: 12)
                            .foregroundColor(Color.gray150)
                            .frame(width: 185, height: 50)
                        Text("내 학급 목록 보러가기")
                            .foregroundColor(Color.black)
                            .font(.custom(pretendard_bold, size: 16))
                    }
                }
            }
        }
        .toastView(toast: $toast)
    }
}

 

 

 

 

+ Recent posts