project/개발 업무

SwiftUI로 Placeholder가 존재하는 TextField 설계 팁 (UIKit호환)

lgvv 2024. 9. 29. 12:37

SwiftUI로 Placeholder가 존재하는 TextField 설계 팁 (UIKit호환)

 

최근에는 SwiftUI와 UIKit을 정말 많이 혼용해서 사용하고 있음.

특히 몇몇 컴포넌트들은 SwiftUI로 작성한 것들을 UIKit에서 가져다가 사용하는 컴포넌트들이 많은데, 서로 손쉽게 사용할 수 있도록 설계.

 

 

글의 순서

  • 구현 결과 스크린샷
  • 구현 예시 코드
  • SwiftUI Usage
  • UIKit Usage

 

구현 결과 스크린샷

 

 

구현 예시 코드

구현 포인트 

  • @State, @Binding을 외부에 직접 노출시키는 것이 아닌 Delegate를 통해 이벤트를 전달함.
  • 장점: UIKit에서 공통된 코드 규칙으로 편리하게 사용할 수 있음.

 

 

import SwiftUI

/// 리스트에서 사용하는 검색 필드
struct SearchFiled: View {
    
    struct InitialState {
        /// 플레이스홀더 기본 값
        var placeholderText: String
        /// 색상
        var appearance: Appearance
        
        struct Appearance {
            /// 돋보기 색상
            var magnifyingglassImage: Color
            /// xmark 색상
            var xmarkImage: Color
            /// 텍스트 필드 배경 색상
            var textFieldBackground: Color
            /// 텍스트 필드 강조 색상
            var textFieldAccent: Color
            /// 플레이스 홀더 색상
            var placeHolderText: Color
        }
    }
    
    /// 검색할 단어
    @State private var searchText: String = ""

    enum DelegateAction {
        /// 전체 지우는 버튼 탭
        case clearButtonTaaped
        /// 검색 단어가 변화했을 때
        case onChangeSearchText(oldValue: String, newValue: String)
        /// 텍스트 필드 onSubmit
        case onSubmitTextField
    }

    /// 초기값
    private var initialState: InitialState
    /// 외부로 내보낼 액션
    private(set) var delegate: ((DelegateAction) -> ())?
    
    init(
        initialState: InitialState,
        delegate: ((DelegateAction) -> Void)? = nil
    ) {
        self.delegate = delegate
        self.initialState = initialState
    }
    
    var body: some View {
        HStack {
            Image(systemName: "magnifyingglass")
                .foregroundStyle(.gray)
            
            ZStack(alignment: .leading) {
                if searchText.isEmpty {
                    Text(initialState.placeholderText)
                        .foregroundStyle(initialState.appearance.placeHolderText)
                }
                
                TextField("", text: $searchText)
                    .accentColor(initialState.appearance.textFieldAccent)
                    .onSubmit {
                        delegate?(.onSubmitTextField)
                    }
                    .onChange(of: searchText) { newValue in
                        delegate?(.onChangeSearchText(oldValue: searchText, newValue: newValue))
                    }
            }
            
            if !searchText.isEmpty {
                Button {
                    searchText = ""
                    delegate?(.clearButtonTaaped)
                } label: {
                    withAnimation(.smooth) {
                        Image(systemName: "xmark.circle")
                            .foregroundStyle(initialState.appearance.xmarkImage)
                    }
                }
            }
        }
        .padding(.horizontal, 12)
        .frame(height: 40)
        .background(
            RoundedRectangle(cornerRadius: 12)
                .foregroundStyle(initialState.appearance.textFieldBackground)
        )
        .padding(.horizontal, 12)
        .padding(.vertical, 8)
        .background(
            Color.clear
        )
    }
}

 

 

 

SwiftUI 사용법 (Usage)

 

SwiftUI의 경우 해당 코드처럼 작성해 사용

  • 초기 상태는 initialState로 주입하며, @Binding 대신 내부 이벤트를 delegate를 활용해 처리
  • 이렇게 할 경우 외부에서 굳이 처리할 필요 없는 @State, Publisher를 작성하도록 강제하지 않아도 되어서 외부 코드가 더 깔끔해짐.

 

SwiftUI

 

 

 

UIKit 사용법 (Usage)

UIKit의 경우 해당 코드처럼 작성해 사용

  • UIKit도 SwiftUI와 동일하게 사용할 수 있고 이벤트는 viewModel로 넘겨 처리 가능