feat: 커뮤니티 글쓰기/수정

- 이미지 gif 등록 기능 추가
This commit is contained in:
Yu Sung
2025-07-03 18:01:06 +09:00
parent 419feeab6d
commit da3ca96960
4 changed files with 123 additions and 104 deletions

View File

@@ -6,7 +6,9 @@
// //
import SwiftUI import SwiftUI
import PhotosUI
import Kingfisher import Kingfisher
import SDWebImageSwiftUI
struct CreatorCommunityModifyView: View { struct CreatorCommunityModifyView: View {
@@ -14,7 +16,7 @@ struct CreatorCommunityModifyView: View {
@StateObject var keyboardHandler = KeyboardHandler() @StateObject var keyboardHandler = KeyboardHandler()
@StateObject private var viewModel = CreatorCommunityModifyViewModel() @StateObject private var viewModel = CreatorCommunityModifyViewModel()
@State private var isShowPhotoPicker = false @State private var selectedItem: PhotosPickerItem? = nil
let onSuccess: () -> Void let onSuccess: () -> Void
var body: some View { var body: some View {
@@ -26,56 +28,66 @@ struct CreatorCommunityModifyView: View {
ScrollView(.vertical, showsIndicators: false) { ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) { VStack(spacing: 0) {
VStack(spacing: 0) { VStack(spacing: 13.3) {
Text("이미지") Text("이미지")
.font(.custom(Font.bold.rawValue, size: 16.7)) .font(.custom(Font.bold.rawValue, size: 16.7))
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
ZStack { PhotosPicker(
if let selectedImage = viewModel.postImage { selection: $selectedItem,
Image(uiImage: selectedImage) matching: .any(of: [.images]),
.resizable() photoLibrary: .shared()) {
.scaledToFill() ZStack(alignment: .bottomTrailing) {
.frame(width: 107, height: 107) if let selectedImage = viewModel.postImageData {
.background(Color(hex: "3e3358")) AnimatedImage(data: selectedImage)
.cornerRadius(8) .resizable()
.clipped() .scaledToFill()
} else if let postImageUrl = viewModel.postImageUrl { .frame(width: 300)
KFImage(URL(string: postImageUrl)) .cornerRadius(8)
.cancelOnDisappear(true) .clipped()
.downsampling( } else if let postImageUrl = viewModel.postImageUrl {
size: CGSize( AnimatedImage(url: URL(string: postImageUrl))
width: 107, .resizable()
height: 107 .scaledToFill()
) .frame(width: 300)
) .cornerRadius(8)
.resizable() .clipped()
.scaledToFill() } else {
.frame(width: 107, height: 107) Image("ic_logo2")
.background(Color(hex: "3e3358")) .resizable()
.cornerRadius(8) .scaledToFit()
.clipped() .padding(13.3)
} else { .frame(width: 107, height: 107)
Image("ic_logo2") .background(Color.bg)
.resizable() .cornerRadius(8)
.scaledToFit() .clipped()
.padding(13.3) }
.frame(width: 107, height: 107)
.background(Color(hex: "13181B"))
.cornerRadius(8)
.clipped()
}
Image("ic_camera") Image("ic_camera")
.padding(10) .padding(10)
.background(Color(hex: "3BB9F1")) .background(Color.button)
.cornerRadius(30) .cornerRadius(30)
.offset(x: 50, y: 36) .offset(x: 15, y: 0)
} }
.frame(alignment: .bottomTrailing) .frame(alignment: .bottomTrailing)
.contentShape(Rectangle()) }
.onTapGesture { isShowPhotoPicker = true } .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)")
}
}
}
}
HStack(alignment: .top, spacing: 0) { HStack(alignment: .top, spacing: 0) {
Text("") Text("")
@@ -230,14 +242,6 @@ struct CreatorCommunityModifyView: View {
} }
} }
} }
if isShowPhotoPicker {
ImagePicker(
isShowing: $isShowPhotoPicker,
selectedImage: $viewModel.postImage,
sourceType: .photoLibrary
)
}
} }
.onTapGesture { hideKeyboard() } .onTapGesture { hideKeyboard() }
.edgesIgnoringSafeArea(.bottom) .edgesIgnoringSafeArea(.bottom)
@@ -249,7 +253,7 @@ struct CreatorCommunityModifyView: View {
.padding(.vertical, 13.3) .padding(.vertical, 13.3)
.frame(width: screenSize().width - 66.7, alignment: .center) .frame(width: screenSize().width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12)) .font(.custom(Font.medium.rawValue, size: 12))
.background(Color(hex: "9970ff")) .background(Color.button)
.foregroundColor(Color.white) .foregroundColor(Color.white)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.cornerRadius(20) .cornerRadius(20)

View File

@@ -20,7 +20,7 @@ final class CreatorCommunityModifyViewModel: ObservableObject {
@Published var content = "" @Published var content = ""
@Published var isAdult = false @Published var isAdult = false
@Published var isAvailableComment = true @Published var isAvailableComment = true
@Published var postImage: UIImage? = nil @Published var postImageData: Data? = nil
@Published var postImageUrl: String? = nil @Published var postImageUrl: String? = nil
@Published private(set) var communityPost: GetCommunityPostListResponse? @Published private(set) var communityPost: GetCommunityPostListResponse?
@@ -95,13 +95,14 @@ final class CreatorCommunityModifyViewModel: ObservableObject {
let jsonData = try? encoder.encode(request) let jsonData = try? encoder.encode(request)
if let jsonData = jsonData { if let jsonData = jsonData {
if let postImage = postImage, let imageData = postImage.jpegData(compressionQuality: 0.8) { if let postImageData = postImageData {
multipartData.append( multipartData.append(
MultipartFormData( MultipartFormData(
provider: .data(imageData), provider: .data(postImageData),
name: "postImage", name: "postImage",
fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000).jpg", fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000)",
mimeType: "image/*") mimeType: "image/*"
)
) )
} }

View File

@@ -6,15 +6,19 @@
// //
import SwiftUI import SwiftUI
import PhotosUI
import SDWebImageSwiftUI
struct CreatorCommunityWriteView: View { struct CreatorCommunityWriteView: View {
@StateObject var keyboardHandler = KeyboardHandler() @StateObject var keyboardHandler = KeyboardHandler()
@StateObject private var viewModel = CreatorCommunityWriteViewModel() @StateObject private var viewModel = CreatorCommunityWriteViewModel()
@State private var selectedItem: PhotosPickerItem? = nil
@State private var isShowRecordingVoiceView = false @State private var isShowRecordingVoiceView = false
@State private var isShowPhotoPicker = false
@State private var fileName: String = "녹음" @State private var fileName: String = "녹음"
let onSuccess: () -> Void let onSuccess: () -> Void
var body: some View { var body: some View {
@@ -26,41 +30,59 @@ struct CreatorCommunityWriteView: View {
ScrollView(.vertical, showsIndicators: false) { ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) { VStack(spacing: 0) {
VStack(spacing: 0) { VStack(spacing: 13.3) {
Text("이미지") Text("이미지")
.font(.custom(Font.bold.rawValue, size: 16.7)) .font(.custom(Font.bold.rawValue, size: 16.7))
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
ZStack { PhotosPicker(
if let selectedImage = viewModel.postImage { selection: $selectedItem,
Image(uiImage: selectedImage) matching: .any(of: [.images]),
.resizable() photoLibrary: .shared()) {
.scaledToFill() ZStack(alignment: .bottomTrailing) {
.frame(width: 107, height: 107) if let selectedImage = viewModel.postImageData {
.background(Color(hex: "3e3358")) AnimatedImage(data: selectedImage)
.cornerRadius(8) .resizable()
.clipped() .scaledToFill()
} else { .frame(width: 300)
Image("ic_logo2") .cornerRadius(8)
.resizable() .clipped()
.scaledToFit() } else {
.padding(13.3) Image("ic_logo2")
.frame(width: 107, height: 107) .resizable()
.background(Color.bg) .scaledToFit()
.cornerRadius(8) .padding(13.3)
.clipped() .frame(width: 107, height: 107)
} .background(Color.bg)
.cornerRadius(8)
.clipped()
}
Image("ic_camera") Image("ic_camera")
.padding(10) .padding(10)
.background(Color.button) .background(Color.button)
.cornerRadius(30) .cornerRadius(30)
.offset(x: 50, y: 36) .offset(x: 15, y: 0)
} }
.frame(alignment: .bottomTrailing) .frame(alignment: .bottomTrailing)
.contentShape(Rectangle()) }
.onTapGesture { isShowPhotoPicker = true } .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)")
}
}
}
}
HStack(alignment: .top, spacing: 0) { HStack(alignment: .top, spacing: 0) {
Text("") Text("")
@@ -72,9 +94,9 @@ struct CreatorCommunityWriteView: View {
.foregroundColor(Color.gray77) .foregroundColor(Color.gray77)
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.top, 24) .padding(.top, 16)
if let _ = viewModel.postImage { if let _ = viewModel.postImageData {
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("오디오 녹음") Text("오디오 녹음")
@@ -190,7 +212,7 @@ struct CreatorCommunityWriteView: View {
.padding(.top, 26.7) .padding(.top, 26.7)
} }
if let _ = viewModel.postImage { if let _ = viewModel.postImageData {
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("가격 설정") Text("가격 설정")
.font(.custom(Font.bold.rawValue, size: 16.7)) .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 { if isShowRecordingVoiceView {
CreatorCommunityRecordingVoiceView( CreatorCommunityRecordingVoiceView(
isShowing: $isShowRecordingVoiceView, isShowing: $isShowRecordingVoiceView,

View File

@@ -28,7 +28,7 @@ final class CreatorCommunityWriteViewModel: ObservableObject {
} }
} }
@Published var isAvailableComment = true @Published var isAvailableComment = true
@Published var postImage: UIImage? = nil @Published var postImageData: Data? = nil
@Published var priceString = "0" { @Published var priceString = "0" {
didSet { didSet {
@@ -60,12 +60,12 @@ final class CreatorCommunityWriteViewModel: ObservableObject {
let jsonData = try? encoder.encode(request) let jsonData = try? encoder.encode(request)
if let jsonData = jsonData { if let jsonData = jsonData {
if let postImage = postImage, let imageData = postImage.jpegData(compressionQuality: 0.8) { if let postImageData = postImageData {
multipartData.append( multipartData.append(
MultipartFormData( MultipartFormData(
provider: .data(imageData), provider: .data(postImageData),
name: "postImage", name: "postImage",
fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000).jpg", fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000)",
mimeType: "image/*" mimeType: "image/*"
) )
) )