Swiftpack.co -  Package - baecheese/LocalizedNumberField
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
baecheese/LocalizedNumberField
from pure number to localized number string
.package(url: "https://github.com/baecheese/LocalizedNumberField.git", from: "ver1.0.0")

LocalizedNumberField

iOS Swift5.0 Swift-ui MIT

글로벌 서비스를 개발하며, 국가와 언어 별로 상이한 숫자 표기 체계를 경험했습니다. 유저의 다양한 입력 환경에 대응할 수 있는 구현 방식이 무엇인지 고민하고, 이에 대해 정리한 공간입니다.

🌷 Blog Post

🔍 Features

Support

  • iOS 14.1
  • Swift 5.0
  • SwiftUI
  • Swift Package Manager

Preview

Screenshot

  • 숫자 변환 필드 리스트 구현

Detail

  • from locale, to locale 설정 -> textfield input -> Button -> to locale 숫자 포맷으로 변환
    • en_US(English, United States) 숫자에서 ar(Arabic) 숫자로 변경

Exmaple

1. LocalizedNumberFieldDataSource 준비

final class SampleFieldModel: ObservableObject {

    @Published var fieldViewModels: [LocalizedNumberFieldViewModel] = [
        LocalizedNumberFieldViewModel(
            index: 0,
            placeHolder: "숫자를 입력해주세요",
            text: "",
            formatter: LocalizedNumberFormatter(from: .en_US, to: Locale.ko_KR),
            keyboardType: .default
        ),
        LocalizedNumberFieldViewModel(
            index: 1,
            placeHolder: "숫자를 입력해주세요",
            text: "",
            formatter: LocalizedNumberFormatter(from: .en_US, to: Locale.fr_GP),
            keyboardType: .default
        ),
        LocalizedNumberFieldViewModel(
            index: 2,
            placeHolder: "숫자를 입력해주세요",
            text: "",
            formatter: LocalizedNumberFormatter(from: .en_US, to: Locale.ne),
            keyboardType: .default
        ),
        LocalizedNumberFieldViewModel(
            index: 3,
            placeHolder: "숫자를 입력해주세요",
            text: "",
            formatter: LocalizedNumberFormatter(from: .en_US, to: Locale.ar),
            keyboardType: .default
        ),
        LocalizedNumberFieldViewModel(
            index: 4,
            placeHolder: "숫자를 입력해주세요",
            text: "",
            formatter: LocalizedNumberFormatter(from: .en_US, to: Locale.it_CH),
            keyboardType: .default
        )
    ]
}

2. EnvironmentObject로 Sample Data 설정

@main
import SwiftUI

@main
struct LocalizedNumberFieldApp: App {
    
    @StateObject var sampleModel = SampleFieldModel()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(sampleModel)
        }
    }
}
  • 메인에서 샘플데이터를 environmentObject로 설정
ContentView
struct ContentView: View {
    
    @EnvironmentObject var modelData: SampleFieldModel
    
    var body: some View {
        SampleList()
            .environmentObject(modelData)
    }
}

SampleList

import SwiftUI

struct SampleList: View {
    
    @EnvironmentObject var modelData: SampleFieldModel
    
    var body: some View {
        List {
            ForEach(modelData.fieldViewModels) {
                SampleRow(dataSource: $0)
                    .environmentObject(modelData)
                    .buttonStyle(PlainButtonStyle())
            }
        }
    }
}

3. SampleRow - LocalizedNumberFieldView 사용

SampleRow
import SwiftUI

struct SampleRow: View {
    
    var dataSource: LocalizedNumberFieldViewModel
    
    /// textfield endEditing 일 때 상태 받는 곳
    @State private var endEditing: Bool = false
    
    private var title: String {
        return "🌎 from \(dataSource.formatter.fromLocale.identifier) to \(dataSource.formatter.toLocale.identifier)"
    }
    
    var body: some View {
        VStack {
            Spacer()
            Text(title)
                .frame(maxWidth: .infinity, alignment: .leading)
                .font(.system(size: 20.0, weight: .bold, design: .rounded))
            HStack {
                Spacer()
                // ⭐️ use it!
                LocalizedNumberFieldView(dataSource: dataSource, endEditing: $endEditing)
                Button(
                    action: {
                        hideKeyboard() // extension 참고
                    },
                    label: {
                        Text("Button")
                            .font(.body)
                            .fontWeight(.bold)
                            .padding(5.0)
                    }
                )
                .background(Color.yellow)
                Spacer()
            }
             // textfield endEditing되면 변환 결과 보여주기
            Text("   result: \(endEditing ? dataSource.result.description : "none")")
                .frame(maxWidth: .infinity, alignment: .leading)
                .font(.system(size: 18.0, weight: .light, design: .rounded))
            Spacer()
        }
    }
    
}
  • Button 누르면 Hide Keyboard (Finish Editing)
Extension
#if canImport(UIKit)
extension View {
    func hideKeyboard() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}
#endif
LocalizedNumberFieldView
import SwiftUI

struct LocalizedNumberFieldView: View {
    
    @EnvironmentObject var modelData: SampleFieldModel

    var dataSource: LocalizedNumberFieldViewModel
    
    /// textfield endEditing 일 때 상태 변환
    @Binding var endEditing: Bool
    @State private var textfieldBorderColor: UIColor = .systemFill
    
    var body: some View {
        VStack {
            HStack {
                TextField(
                    dataSource.placeHolder,
                    text: $modelData.fieldViewModels[dataSource.index].text,
                    onEditingChanged: { onEditing in
                        if false == onEditing {
                            try? validate(text: dataSource.text)
                        }
                        endEditing = !onEditing
                    }
                )
                .keyboardType(dataSource.keyboardType)
                .border(Color(textfieldBorderColor), width: 1)
            }
        }
    }
    
    /// 변환 결과값
    private func validate(text: String) throws {
        do {
            let localizedNumber = try dataSource.formatter.localizedNumberString(from: text, style: .decimal)
            setResult(text: localizedNumber, result: .success(from: text, to: localizedNumber))
        } catch {
            let formatterError = error as? LocalizedNumberFormatterError
            setResult(text: text, result: .error(formatterError ?? .unknown))
            throw error
        }
    }
    
    private func setResult(text: String, result: LocalizedNumberFormatterResult) {
        dataSource.result = result
        textfieldBorderColor = result.isError ? .systemRed : .systemFill
    }
    
}
  1. 유저가 textfield editing을 끝냈을 때, input 값을 func validate(text: String) throws를 통해 변환

onEditingChanged The action to perform when the user begins editing text and after the user finishes editing text. The closure receives a Boolean value that indicates the editing status: true when the user begins editing, false when they finish.

  1. DataSource에 변환결과(LocalizedNumberFormatterResult) 업데이트
  2. SampleRow에서 @State private var endEditing: Bool이 업데이트
  3. SampleRow에서 Text로 업데이트

참고

GitHub

link
Stars: 1
Last commit: 2 weeks ago

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco