diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyView.swift index 8c698b7..b2dd284 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyView.swift @@ -6,7 +6,9 @@ // import SwiftUI +import PhotosUI import Kingfisher +import SDWebImageSwiftUI struct CreatorCommunityModifyView: View { @@ -14,7 +16,7 @@ struct CreatorCommunityModifyView: View { @StateObject var keyboardHandler = KeyboardHandler() @StateObject private var viewModel = CreatorCommunityModifyViewModel() - @State private var isShowPhotoPicker = false + @State private var selectedItem: PhotosPickerItem? = nil let onSuccess: () -> Void var body: some View { @@ -26,56 +28,66 @@ struct CreatorCommunityModifyView: View { ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 0) { - VStack(spacing: 0) { + VStack(spacing: 13.3) { Text("이미지") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) .frame(maxWidth: .infinity, alignment: .leading) - ZStack { - if let selectedImage = viewModel.postImage { - Image(uiImage: selectedImage) - .resizable() - .scaledToFill() - .frame(width: 107, height: 107) - .background(Color(hex: "3e3358")) - .cornerRadius(8) - .clipped() - } else if let postImageUrl = viewModel.postImageUrl { - KFImage(URL(string: postImageUrl)) - .cancelOnDisappear(true) - .downsampling( - size: CGSize( - width: 107, - height: 107 - ) - ) - .resizable() - .scaledToFill() - .frame(width: 107, height: 107) - .background(Color(hex: "3e3358")) - .cornerRadius(8) - .clipped() - } else { - Image("ic_logo2") - .resizable() - .scaledToFit() - .padding(13.3) - .frame(width: 107, height: 107) - .background(Color(hex: "13181B")) - .cornerRadius(8) - .clipped() + PhotosPicker( + selection: $selectedItem, + matching: .any(of: [.images]), + photoLibrary: .shared()) { + ZStack(alignment: .bottomTrailing) { + if let selectedImage = viewModel.postImageData { + AnimatedImage(data: selectedImage) + .resizable() + .scaledToFill() + .frame(width: 300) + .cornerRadius(8) + .clipped() + } else if let postImageUrl = viewModel.postImageUrl { + AnimatedImage(url: URL(string: postImageUrl)) + .resizable() + .scaledToFill() + .frame(width: 300) + .cornerRadius(8) + .clipped() + } else { + Image("ic_logo2") + .resizable() + .scaledToFit() + .padding(13.3) + .frame(width: 107, height: 107) + .background(Color.bg) + .cornerRadius(8) + .clipped() + } + + Image("ic_camera") + .padding(10) + .background(Color.button) + .cornerRadius(30) + .offset(x: 15, y: 0) + } + .frame(alignment: .bottomTrailing) + } + .onChange(of: selectedItem) { newItem in + Task { + if let item = newItem { + do { + // ✅ 이미지 원본 Data 가져오기 (GIF 포함) + if let data = try await item.loadTransferable(type: Data.self) { + viewModel.postImageData = data + } + } catch { + viewModel.errorMessage = "이미지를 로드하지 못했습니다." + viewModel.isShowPopup = true + DEBUG_LOG("이미지 로드 실패: \(error)") + } + } + } } - - Image("ic_camera") - .padding(10) - .background(Color(hex: "3BB9F1")) - .cornerRadius(30) - .offset(x: 50, y: 36) - } - .frame(alignment: .bottomTrailing) - .contentShape(Rectangle()) - .onTapGesture { isShowPhotoPicker = true } HStack(alignment: .top, spacing: 0) { Text("※ ") @@ -230,14 +242,6 @@ struct CreatorCommunityModifyView: View { } } } - - if isShowPhotoPicker { - ImagePicker( - isShowing: $isShowPhotoPicker, - selectedImage: $viewModel.postImage, - sourceType: .photoLibrary - ) - } } .onTapGesture { hideKeyboard() } .edgesIgnoringSafeArea(.bottom) @@ -249,7 +253,7 @@ struct CreatorCommunityModifyView: View { .padding(.vertical, 13.3) .frame(width: screenSize().width - 66.7, alignment: .center) .font(.custom(Font.medium.rawValue, size: 12)) - .background(Color(hex: "9970ff")) + .background(Color.button) .foregroundColor(Color.white) .multilineTextAlignment(.center) .cornerRadius(20) diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyViewModel.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyViewModel.swift index 47a1d4b..88d0d45 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyViewModel.swift @@ -20,7 +20,7 @@ final class CreatorCommunityModifyViewModel: ObservableObject { @Published var content = "" @Published var isAdult = false @Published var isAvailableComment = true - @Published var postImage: UIImage? = nil + @Published var postImageData: Data? = nil @Published var postImageUrl: String? = nil @Published private(set) var communityPost: GetCommunityPostListResponse? @@ -95,13 +95,14 @@ final class CreatorCommunityModifyViewModel: ObservableObject { let jsonData = try? encoder.encode(request) if let jsonData = jsonData { - if let postImage = postImage, let imageData = postImage.jpegData(compressionQuality: 0.8) { + if let postImageData = postImageData { multipartData.append( MultipartFormData( - provider: .data(imageData), + provider: .data(postImageData), name: "postImage", - fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000).jpg", - mimeType: "image/*") + fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000)", + mimeType: "image/*" + ) ) } diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteView.swift index 9955795..008ad68 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteView.swift @@ -6,15 +6,19 @@ // import SwiftUI +import PhotosUI +import SDWebImageSwiftUI struct CreatorCommunityWriteView: View { @StateObject var keyboardHandler = KeyboardHandler() @StateObject private var viewModel = CreatorCommunityWriteViewModel() + @State private var selectedItem: PhotosPickerItem? = nil + @State private var isShowRecordingVoiceView = false - @State private var isShowPhotoPicker = false @State private var fileName: String = "녹음" + let onSuccess: () -> Void var body: some View { @@ -26,41 +30,59 @@ struct CreatorCommunityWriteView: View { ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 0) { - VStack(spacing: 0) { + VStack(spacing: 13.3) { Text("이미지") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color.grayee) .frame(maxWidth: .infinity, alignment: .leading) - ZStack { - if let selectedImage = viewModel.postImage { - Image(uiImage: selectedImage) - .resizable() - .scaledToFill() - .frame(width: 107, height: 107) - .background(Color(hex: "3e3358")) - .cornerRadius(8) - .clipped() - } else { - Image("ic_logo2") - .resizable() - .scaledToFit() - .padding(13.3) - .frame(width: 107, height: 107) - .background(Color.bg) - .cornerRadius(8) - .clipped() + PhotosPicker( + selection: $selectedItem, + matching: .any(of: [.images]), + photoLibrary: .shared()) { + ZStack(alignment: .bottomTrailing) { + if let selectedImage = viewModel.postImageData { + AnimatedImage(data: selectedImage) + .resizable() + .scaledToFill() + .frame(width: 300) + .cornerRadius(8) + .clipped() + } else { + Image("ic_logo2") + .resizable() + .scaledToFit() + .padding(13.3) + .frame(width: 107, height: 107) + .background(Color.bg) + .cornerRadius(8) + .clipped() + } + + Image("ic_camera") + .padding(10) + .background(Color.button) + .cornerRadius(30) + .offset(x: 15, y: 0) + } + .frame(alignment: .bottomTrailing) + } + .onChange(of: selectedItem) { newItem in + Task { + if let item = newItem { + do { + // ✅ 이미지 원본 Data 가져오기 (GIF 포함) + if let data = try await item.loadTransferable(type: Data.self) { + viewModel.postImageData = data + } + } catch { + viewModel.errorMessage = "이미지를 로드하지 못했습니다." + viewModel.isShowPopup = true + DEBUG_LOG("이미지 로드 실패: \(error)") + } + } + } } - - Image("ic_camera") - .padding(10) - .background(Color.button) - .cornerRadius(30) - .offset(x: 50, y: 36) - } - .frame(alignment: .bottomTrailing) - .contentShape(Rectangle()) - .onTapGesture { isShowPhotoPicker = true } HStack(alignment: .top, spacing: 0) { Text("※ ") @@ -72,9 +94,9 @@ struct CreatorCommunityWriteView: View { .foregroundColor(Color.gray77) } .frame(maxWidth: .infinity) - .padding(.top, 24) + .padding(.top, 16) - if let _ = viewModel.postImage { + if let _ = viewModel.postImageData { VStack(spacing: 13.3) { HStack(spacing: 0) { Text("오디오 녹음") @@ -190,7 +212,7 @@ struct CreatorCommunityWriteView: View { .padding(.top, 26.7) } - if let _ = viewModel.postImage { + if let _ = viewModel.postImageData { VStack(spacing: 13.3) { Text("가격 설정") .font(.custom(Font.bold.rawValue, size: 16.7)) @@ -308,14 +330,6 @@ struct CreatorCommunityWriteView: View { } } - if isShowPhotoPicker { - ImagePicker( - isShowing: $isShowPhotoPicker, - selectedImage: $viewModel.postImage, - sourceType: .photoLibrary - ) - } - if isShowRecordingVoiceView { CreatorCommunityRecordingVoiceView( isShowing: $isShowRecordingVoiceView, diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteViewModel.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteViewModel.swift index 3f4f949..bfe3588 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteViewModel.swift @@ -28,7 +28,7 @@ final class CreatorCommunityWriteViewModel: ObservableObject { } } @Published var isAvailableComment = true - @Published var postImage: UIImage? = nil + @Published var postImageData: Data? = nil @Published var priceString = "0" { didSet { @@ -60,12 +60,12 @@ final class CreatorCommunityWriteViewModel: ObservableObject { let jsonData = try? encoder.encode(request) if let jsonData = jsonData { - if let postImage = postImage, let imageData = postImage.jpegData(compressionQuality: 0.8) { + if let postImageData = postImageData { multipartData.append( MultipartFormData( - provider: .data(imageData), + provider: .data(postImageData), name: "postImage", - fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000).jpg", + fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000)", mimeType: "image/*" ) )