feat: 커뮤니티 글쓰기/수정
- 이미지 gif 등록 기능 추가
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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/*"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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/*"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user