질문&답변
소리가 ㅠㅠ
안녕하세요 김상관 님. 먼저 질문 확인이 늦어서 늦게 답변드린점 죄송합니다. 해당강의 영상 확인 하였습니다. 음성이 한쪽으로 나오는 현상 확인 하여서 삭제 후 동영상 재 인코딩 하여 업로드 하였습니다. 강의 오류 알려주셔서 감사합니다 제이콥
- 좋아요수
- 0
- 댓글수
- 2
- 조회수
- 71
질문&답변
안녕하세요 김상관 님. 먼저 질문 확인이 늦어서 늦게 답변드린점 죄송합니다. 해당강의 영상 확인 하였습니다. 음성이 한쪽으로 나오는 현상 확인 하여서 삭제 후 동영상 재 인코딩 하여 업로드 하였습니다. 강의 오류 알려주셔서 감사합니다 제이콥
질문&답변
Index와 IndexSet 차이점 설명 안녕하세요 최수훈님, index 와 IndexSet 의 차이점에 대해 설명해드릴게요. Index vs IndexSet 차이점 1. Index (Int 타입) // 하나의 숫자만 가리킴 let index = 2 // 배열의 2번째 요소 fruitArray.remove(at: index) // 하나만 삭제 2. IndexSet // 여러 개의 인덱스를 담을 수 있는 집합 let indexSet: IndexSet = [0, 2, 4] // 0번, 2번, 4번 요소들 fruitArray.remove(atOffsets: indexSet) // 여러 개 동시 삭제 왜 remove(atOffsets:)를 사용하나요? SwiftUI의 List에서 .onDelete 를 사용할 때는 사용자가 여러 행을 선택해서 한 번에 삭제 할 수 있기 때문입니다. List { ForEach(fruitArray, id: \.name) { fruit in Text("\(fruit.name) - \(fruit.count)개") } .onDelete(perform: deleteFruit) // 여기서 IndexSet이 자동으로 전달됨 } 실제 동작 예시 사용자가 편집 모드에서: 딸기(0번), 바나나(2번)를 동시 선택 → IndexSet [0, 2]가 전달 한 번에 여러 개 삭제 가능 정리 구분 Index (Int) IndexSet 용도 단일 항목 삭제 다중 항목 삭제 메서드 remove(at:) remove(atOffsets:) 값 예시 2 [0, 2, 4] 감사합니다 제이콥
질문&답변
안녕하세요 gaeun 님. @ObservationIgnored + @Published 사용 이유는 두개의 서로 다른 반응형 시스템을 함께 사용하기위해서 테스트 용으로 사용했습니다. 각각의 역할 @Published (Combine) 목적 : Combine 연산자 사용 ( .debounce() , .map() 등) 용도 : 비즈니스 로직 처리 (유효성 검사, 데이터 변환) @Observable (SwiftUI) 목적 : View 자동 업데이트 용도 : UI 반영 왜 @ObservationIgnored가 필요한가? @ObservationIgnored @Published var textFieldID: String = "" var idValid: Bool = false ❌ @ObservationIgnored 없으면 textFieldID 변경 → 즉시 View 업데이트 .debounce() 가 무의미해짐 불필요한 중복 업데이트 발생 ✅ @ObservationIgnored 사용하면 textFieldID → Combine 파이프라인만 실행 처리 결과 ( idValid ) → View 업데이트 효율적인 업데이트 제어 실제 동작 흐름 사용자 입력 ↓ textFieldID (@Published) ↓ Combine 파이프라인 (.debounce → .map → .sink) ↓ idValid = true/false ↓ View 자동 업데이트 (@Observable) 결론: Combine의 강력한 연산자를 활용하면서, SwiftUI의 새로운 @Observable로 View를 효율적으로 업데이트하기 위한 하이브리드 방식 입니다. 감사합니다 Jacob
질문&답변
안녕하세요. nonehour 님 에러 발생 원인 현재 코드에서 두 가지 주요 문제가 발생하고 있습니다: ObservableObject 프로토콜 준수 실패 : UserViewModel 클래스가 ObservableObject 를 채택했지만 필요한 요구사항을 충족하지 못하고 있습니다. Combine 프레임워크 누락 : @Published 프로퍼티 래퍼와 관련된 초기화자를 찾을 수 없다는 에러가 발생합니다. 해결 방법 파일 상단의 import 구문을 다음과 같이 수정해주세요: import SwiftUI import Combine // 이 줄을 추가해주세요 왜 이런 차이가 발생하는가 이런 현상이 발생하는 이유는 개발 환경의 차이 때문입니다: Xcode 버전 차이 : 최신 Xcode에서는 SwiftUI를 import할 때 Combine도 함께 자동으로 가져오는 경우가 있지만, 구버전이나 특정 설정에서는 그렇지 않을 수 있습니다. iOS 배포 타겟 : 프로젝트의 iOS 최소 지원 버전에 따라 자동 import 동작이 달라질 수 있습니다. 프로젝트 설정 : 프로젝트 생성 방식이나 빌드 설정에 따라 모듈 import 방식이 다를 수 있습니다. 근본적인 이해 @Published 와 ObservableObject 는 실제로 Combine 프레임워크의 핵심 구성 요소입니다. 따라서 이들을 사용할 때는 명시적으로 Combine을 import하는 것이 가장 안전하고 명확한 방법입니다. 강의 예제와 달라서 에러가 발생된점 양해 드립니다. 감사합니다. Jacob
질문&답변
안녕하세요 최수훈 님. 제가 질문 확인이 늦어서 답변이 이제 드립니다. .NavigationBarItems 는 Deprecated 되어서 .toolbar() 사용하게끔 권장되고 있습니다. 질문주신 ListBasic 부분에서 EditButton() 을 사용했는데, 변경된 코드는 다음과 같습니다. // 기존 코드 .navigationBarItems(leading: EditButton(), trailing: addButton) // 변경된 코드 .toolbar() 사용 .toolbar { ToolbarItem(placement: .navigationBarLeading) { EditButton() } ToolbarItem(placement: .navigationBarTrailing) { addButton } 다른 NavigationBarItems 사용한 강의 예제들도 .toolbar{} 를 사용하는 방식으로 변경하여 강의 제공 예제 파일에 업데이트 하도록 하겠습니다. 감사합니다 제이콥
질문&답변
안녕하세요 ducduck 님. 질문 주셔서 감사합니다. 정리하자면 제가 설명한 부분 : LocalViewBuilder에 선언된 ViewType을 사용해서 조건부 뷰를 만들기 위해 @ViewBuilder가 필요하다. 질문자님이 이해하신 부분 : 각 조건에서 반환하는 뷰들이 서로 다른 타입(Text, VStack, Image)이라서 @ViewBuilder가 필요하다. 실제로는 둘 다 연결된 같은 이유입니다! ViewType enum을 사용해서 → 조건부 분기를 만들고 → 각 분기에서 다른 타입의 뷰를 반환하기 때문에 → @ViewBuilder가 필요 예시로 @ViewBuilder private var numberPlate: some View { if type == .one { Text("1") } // Text 타입 else if type == .two { VStack{} } // VStack 타입 else { Image("3") } // Image 타입 } SwiftUI에서 some View 는 단일 타입만 반환할 수 있는데, @ViewBuilder 가 이런 다른 타입들을 하나로 통합해주는 역할을 합니다. 그렇게 때문에 ViewType 을 활용한 조건부 뷰 구현과 서로 다른 타입 처리, 둘 다 @ViewBuilde r 가 필요한 이유입니다
질문&답변
안녕하세요 ycc3819 님. 질문해주신 @Observable을 활용한 코드에서, 클릭 시 ~가 View에 바로 반영되지 않고, 앱을 재시작하거나 새로운 데이터를 입력할 때 한 번에 업데이트되는 현상을 발견했습니다. 이 문제를 해결하기 위해 여러 시도를 해본 결과, @State와 @StateObject의 차이 로 인해 Core Data와 View의 상태가 즉시 동기화되지 않았음을 알게 되었습니다. 1. Core Data에서 @StateObject를 사용하는 이유 • Core Data의 상태는 View가 재생성되더라도 유지되어야 합니다. 따라서, @StateObject로 ViewModel을 관리하는 것이 적합합니다. • @StateObject는 ViewModel과 View의 생명주기를 연결하고, 상태가 변경될 때 View에 자동으로 반영되도록 보장합니다. 2. @State와 @StateObject의 선택 기준 • @State : • 단순 상태(문자열, 정수, 부울 등)를 관리할 때 적합. • View 외부와의 상태 공유가 필요하지 않은 경우 사용. • @StateObject : • Core Data, 비동기 작업, ViewModel 등 객체 기반 상태 관리가 필요할 때 적합. • View와 ViewModel의 생명주기를 연결하고 상태를 유지. 3. 적용 예시 • Core Data와 같은 외부 데이터베이스를 관리할 때는 ViewModel에서 Core Data를 초기화하고, @StateObject를 통해 ViewModel과 View를 연결해야 데이터가 재사용되고 상태가 유지됩니다. 저 역시 질문을 통해 새로운 관점을 배울 수 있었고, 여러 시도를 통해 이 문제를 해결할 수 있었습니다. 질문해주셔서 감사드리며, 앞으로도 궁금한 점이 있으면 언제든지 알려주세요. 감사합니다. Jacob import SwiftUI import CoreData import Observation @Observable // Observable 사용 class CoreDataInterViewModel2 { let container: NSPersistentContainer var savedEntities: [Fish] = [] // @Published 사용 안함 init() { container = NSPersistentContainer(name: "FishContainer") container.loadPersistentStores { (description, error) in if let error = error { print("ERROR LOADING CORE DATA. \(error)") } else { print("SUCCESSFULLY LOADED CORE DATA: \(description)") } } fetchFish() } func fetchFish() { let request = NSFetchRequest (entityName: "Fish") do { savedEntities = try container.viewContext.fetch(request) } catch { print("ERROR FETCHING CORE DATA: \(error)") } } func saveData() { do { try container.viewContext.save() fetchFish() } catch { print("ERROR SAVING CORE DATA: \(error)") } } func addFish(text: String) { let newFish = Fish(context: container.viewContext) newFish.name = text saveData() } func deleteFish(indexSet: IndexSet) { guard let index = indexSet.first else { return } let entity = savedEntities[index] container.viewContext.delete(entity) saveData() } func updateFish(fish: Fish) { let currentName = fish.name ?? "" fish.name = currentName + "~" saveData() } } // MARK: - VIEW struct CoreDataInter2: View { @State private var textFieldText: String = "" @StateObject var vm: CoreDataInterViewModel = .init() // 기존 @StateObject 를 사용 var body: some View { NavigationStack { VStack(spacing: 20) { TextField("새로운 생선을 입력하세요", text: $textFieldText) .textFieldStyle(.roundedBorder) Button { guard !textFieldText.isEmpty else { return } vm.addFish(text: textFieldText) textFieldText = "" } label: { Text("추가하기") .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(8) } List { ForEach(vm.savedEntities, id: \.self) { fish in Text(fish.name ?? "이름 없음") .onTapGesture { vm.updateFish(fish: fish) } } .onDelete { indexSet in vm.deleteFish(indexSet: indexSet) } } .listStyle(.plain) } .padding() .navigationTitle("Fish Market") } } } #Preview { CoreDataInter2() }
질문&답변
안녕하세요 miso lim 님. 제공된 강의 code 파일을 xcode 16.2 버전 (iOS 18) 에서 실행해보니 이상 없이 잘 되었습니다. 혹시 강의 중간마다 에러가 발생 되면 언제든지 질문 주시면 버전에 맞춰서 수정된 코드로 답변해 드리겠습니다. 감사합니다 Jacob
질문&답변
안녕하세요 ycc3819 님. 저도 확인한 결과 @Observable 로 변경할 시 무한 루핑이 발생되는것을 확인하였습니다. 발생한 원인을 보자면.. 1. 무한 루핑 발생 원인 • @State는 값 타입 상태를 관리하기 위해 설계되었습니다. 하지만 @Observable은 클래스 타입(참조 타입)을 기반으로 동작하며, SwiftUI가 상태 변경을 감지할 때 ViewModel이 반복적으로 초기화되는 문제가 발생합니다. • 이로 인해 init과 deinit이 무한히 호출되며 루핑이 발생합니다. 2. 그래서 강의 코드 처럼 @StateObject를 사용해야 하는 이유 • 기존 강의 코드에서 @StateObject는 ObservableObject 와 함께 사용되며, SwiftUI 뷰의 생명주기와 ViewModel의 초기화/해제를 안정적으로 관리합니다. • @StateObject는 뷰가 다시 렌더링되더라도 ViewModel을 재사용하기 때문에 무한 루핑 문제가 발생하지 않습니다. 그래서 강의 내용 코드를 @Observable 로 바꿀때는 강의에서 다루지 않은 .onAppear, .onDisappear 를 사용해서 Weak self 약한 참조 내용을 확인 하실 수 있습니다. (@State, 와 init, deinit 을 사용하게 되면 라이프 사이클 충돌로 인해 무한 루핑이 되기 때문입니다. ) 예시 코드 import SwiftUI import Observation // @Observable 사용을 위해 임포트 // MARK: - VIEWMODEL @Observable class WeakSelfInterViewModel { var data: String? = nil // 데이터를 관리하는 상태 변수 init() { getData() // 초기 데이터 로드 } func getData() { // 비동기로 데이터를 로드하며, 강한 참조 순환을 방지하기 위해 [weak self] 사용 DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in self?.data = "NEW DATA!" // 데이터 업데이트 } } } // MARK: - SCREEN 1 struct WeakSelfInter: View { var body: some View { NavigationStack { // 2번 페이지로 이동하며 ViewModel은 2번 페이지에서만 생성 및 관리 NavigationLink("2번째 페이지로 이동") { WeakSelfInter2() } .navigationTitle("1번째 페이지") // 네비게이션 타이틀 설정 } } } // MARK: - SCREEN 2 struct WeakSelfInter2: View { @State private var vm = WeakSelfInterViewModel() // @State로 ViewModel 관리 var body: some View { VStack { Text("2번째 페이지") .font(.largeTitle) .foregroundColor(.red) // ViewModel의 데이터가 있으면 표시 if let data = vm.data { Text(data) } } .onAppear { // 뷰가 나타날 때 데이터 로드 확인 print("뷰가 나타남") } .onDisappear { // 뷰가 사라질 때 로그 출력 print("뷰가 사라짐") // ViewModel은 @State로 관리되므로 자동으로 메모리에서 해제됨 } } } // MARK: - PREVIEW #Preview { WeakSelfInter() // 미리보기에서 WeakSelfInter 화면 확인 }
질문&답변
안녕하세요 이재영 님. 애플 공식문서 조회 하는 것에 대해서 몇가지 저의 팁을 드리자면 https://developer.apple.com/documentation/ 애플 문서는 주제별로 잘 정리 되어 있지만, 특정 기능을 바로 찾기 위해서는 먼저 개념 파악 이 중요합니다. 예를 들어, SwiftUI에서 뷰 를 다룬다면 “View Protocol”로 검색을 시작하는 것이 유용합니다. 키워드 간결화 : 문서 검색은 구체적인 용어를 간단하게 입력할수록 정확한 결과를 얻을 수 있습니다. 예를 들어 “SwiftUI delete text items in loop”보다는 “SwiftUI List delete” 처럼 핵심 단어로 줄여서 검색해보세요. 메서드 이름, 프로토콜 사용 : Swift나 SwiftUI에서 제공하는 메서드나 프로토콜 이름을 직접 검색하면 유용합니다. 예를 들어, “ForEach” 를 이용한 반복 처리를 문서에서 찾아보고, 그 예시를 통해 구현 방법을 이해할 수 있습니다. 예를 들어, 반복문을 통해 텍스트를 한 번에 지우는 기능 을 찾고자 한다면: • 먼저 “SwiftUI ForEach” 를 검색합니다. 이 반복문 구조에 대한 문서를 찾고, ForEach 내부에서 데이터를 처리하는 방법을 이해할 수 있습니다. • 그다음으로 “SwiftUI delete items” 나 “SwiftUI onDelete” 와 같은 키워드로 검색합니다. SwiftUI의 List에서 항목을 삭제하는 기능은 주로 onDelete() 메서드로 처리합니다. • 이때 관련 문서를 찾으면, 다양한 예제와 함께 사용법을 배울 수 있습니다. 예를 들어, ForEach 와 onDelete() 를 결합하면 리스트의 여러 텍스트 항목을 반복적으로 삭제하는 기능을 구현할 수 있습니다. SwiftData 강의 부분은 오늘이나 내일 중으로 업데이트 예정입니다. 감사합니다. Jacob