SentimentModel
로 설정한 것 이외에는 코드를 작성하지 않은 상태였다.import SwiftUI
struct Main: View {
@State private var showSheet: Bool = false
@State private var tabSelection: Int = 1
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 20) {
calendarTabView
writeButton
}
.navigationTitle("감정 일기")
.padding()
}
}
}
extension Main {
private var calendarTabView: some View {
ZStack {
TabView(selection: $tabSelection) {
Rectangle()
.foregroundColor(.red)
.tag(1)
Rectangle()
.foregroundColor(.orange)
.tag(2)
Rectangle()
.foregroundColor(.yellow)
.tag(3)
Rectangle()
.foregroundColor(.green)
.tag(4)
Rectangle()
.foregroundColor(.blue)
.tag(5)
}
.frame(height: 400)
.tabViewStyle(.page)
Text("달력 형태의 일기 -> Grid 뷰입니다")
}
}
private var writeButton: some View {
Button {
showSheet.toggle()
} label: {
Text("오늘 일기 작성 -> 텍스트 작성 모달")
.font(.headline)
.fontWeight(.semibold)
.withDefaultViewModifier()
}
.sheet(isPresented: $showSheet) {
TextInput(dataServiceEnum: .ClovaSentimentDataService)
}
}
}
import SwiftUI
struct DefaultButtonViewModifier: ViewModifier {
let backgroundColor: Color
func body(content: Content) -> some View {
content
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(backgroundColor)
.cornerRadius(10)
.padding(.horizontal, 40)
}
}
extension View {
func withDefaultViewModifier(_ backgroundColor: Color = Color.blue.opacity(0.7)) -> some View {
modifier(DefaultButtonViewModifier(backgroundColor: backgroundColor))
}
}
import SwiftUI
struct TextInput: View {
@StateObject private var viewModel: TextInputViewModel
init(dataServiceEnum: DataServiceEnum) {
_viewModel = StateObject(wrappedValue: TextInputViewModel(dataService: dataServiceEnum.dataService))
UITextView.appearance().backgroundColor = .clear
}
var body: some View {
VStack {
TextEditor(text: $viewModel.inputText)
.background(Color.gray.opacity(0.2))
.font(.title)
.cornerRadius(10)
.padding()
// Sentiment Label -> Text and Image
if let sentimentAnalysis = viewModel.sentimentAnalysis {
if let document = sentimentAnalysis.document {
Text(document.sentiment)
.font(.headline)
.fontWeight(.semibold)
.withDefaultViewModifier()
}
Image(sentimentAnalysis.labelImageString)
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
Text(sentimentAnalysis.labelString)
.font(.headline)
.fontWeight(.semibold)
}
Button {
viewModel.fetchSentimentAnalysis()
} label: {
Text("GET DATA!")
.font(.headline)
.fontWeight(.semibold)
.withDefaultViewModifier()
.padding(.bottom, 10)
}
}
}
}
ObservableClass
인 TextInputViewModel
을 StateObject
로 받기 때문에 뷰 모델의 변화값을 계속해서 관찰 가능inputText
에 텍스트를 기록 가능 → Published
되어 있는 sentimentAnalysis
데이터의 변화를 구독하고 있기 때문에 곧바로 패치받은 결과값을 UI에 그리기import SwiftUI
import Combine
class TextInputViewModel: ObservableObject {
@Published var sentimentAnalysis: SentimentModel? = nil
@Published var inputText: String = ""
let dataService: DataService
var cancellables = Set<AnyCancellable>()
init(dataService: DataService) {
self.dataService = dataService
addSubscribe()
}
private func addSubscribe() {
dataService.sentimentAnalysisPublisher
.sink { [weak self] returnedData in
guard let self = self else { return }
DispatchQueue.main.async {
self.sentimentAnalysis = returnedData
}
}
.store(in: &cancellables)
}
func fetchSentimentAnalysis() {
guard inputText.count > 0 && inputText.count <= 1000 else { return }
dataService.fetchSentimentAnalysis(inputText)
}
}
Published
된 값을 구독하는 함수 → 비동기 데이터를 다루기 때문에 약한 참조 및 디스패치 메인 큐에서 작성Combine
프레임워크 사용inputText
를 통해 전달 및 사용하고 있기 때문에 별도의 Binding
으로 넘겨줄 필요 없음데이터 의존성 주입을 통해 뷰 모델을 생성할 때 주입한 종류의 데이터 서비스를 사용할 수 있도록 한 게 본 구현의 포인트. 특히 데이터 서비스 클래스가 다양하기 때문에 프로토콜을 따르게 함으로써 다양한 조율의 서비스 클래스를 사용 가능하다!