SwiftUI로 이모지 키보드 만들기

  • SwiftUI에서 이모지 키보드를 만들어봤다.
  • 카톡이나 메신저 앱에서 많이 사용되는 UI다.
swiftui-emoji-keyboard swiftui-emoji-keyboard
  • 데이터를 위한 구조체 Emoji와 Package를 만든다.
  • 실제 이모지 대신 랜덤 색상을 사용 할 것이다.
  • Emoji와 Package는 SwiftUI의 ForEach에 사용되어서 필요한 프로토콜을 준수했다.
struct Emoji: Identifiable {
    let id = UUID()
    let sample = Color.random()
}

struct Package: Identifiable, Equatable, Hashable {
    let id = UUID()
    let preview = Color.random()
    let emojis = (0..<30).map { _ in Emoji() }
    
    static func == (lhs: Package, rhs: Package) -> Bool {
        lhs.id == rhs.id
    }
    
    public func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

extension Color {
    static func random() -> Color {
        Color(
            red: .random(in: 0...1),
            green: .random(in: 0...1),
            blue: .random(in: 0...1),
            opacity: 1
        )
    }
}
  • TextField와 이모지 키보드 활성화 버튼을 추가했다.
  • TextField를 탭해 키보드를 활성화 하면, keyboardPublisher로 현재 키보드의 높이를 가져온다.
  • 그리고 이모지 키보드 버튼을 누르면, 기존 키보드는 비활성화 되고 같은 높이의 이모지 키보드가 나타난다.
  • 이질감을 줄이기 위해, 이모지 키보드는 기존 키보드와 같은 위치에 표시하고 있다.
struct ContentView: View {
    @Environment(\.safeAreaInsets) var safeAreaInsets
    
    @FocusState private var keyboardShown: Bool
    @State private var text = ""
    @State private var height: CGFloat = 0
    @State private var emojiShown = false
    
    private var keyboardPublisher: NotificationCenter.Publisher {
        NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
    }
    
    var body: some View {
        GeometryReader { _ in
            VStack {
                Spacer()
                HStack {
                    TextField("Sample emoji keyboard", text: $text)
                        .focused($keyboardShown)
                    
                    Image(.emoji)
                        .onTapGesture {
                            keyboardShown.toggle()
                            emojiShown.toggle()
                        }
                }
                Spacer()
            }
            .padding(.horizontal)
        }
        .ignoresSafeArea()
        .safeAreaInset(edge: .bottom) {
            if emojiShown {
                EmojiKeyboardView()
                    .frame(height: height)
                    .background(.black)
                    .transaction { trans in
                        trans.animation = nil
                    }
            }
        }
        .onReceive(keyboardPublisher) { notification in
            guard let size = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
            height = size.height - safeAreaInsets.bottom
        }
    }
}
  • 이제부터는 이모지 키보드 그 자체에 대한 구현이다.
  • data는 임의로 생성하고, 필요한 GridItem도 만들었다.
  • 이모지 키보드는 크게 packageView와 emojiView로 이뤄져있다.
struct EmojiKeyboardView: View {
    @State private var tabIndex = 0
    
    let data = Array((0..<15).map { _ in Package() }.enumerated())
    let gridItem = GridItem(.adaptive(minimum: 50), spacing: 16)
    
    var body: some View {
        VStack(spacing: 0) {
            packageView
            emojiView
        }
    }
}
  • packageView는 이모지 키보드의 상단에 위치하고, 이모지 패키지의 프리뷰를 보여주는 UI다.
  • 가로 스크롤뷰를 사용해 Package의 preview를 표시하고 있다.
  • 탭 했을때는 아래 나올 emojiView의 탭을 바꿔주고 있다.
  • tabIndex가 변경 되었을때는 opacity를 통해 선택 여부를 표시해주고 있다.
extension EmojiKeyboardView {
    var packageView: some View {
        ScrollView(.horizontal) {
            HStack(spacing: AppPadding.xs) {
                ForEach(data, id: \.element) { index, package in
                    package.preview
                        .frame(width: 35, height: 35)
                        .clipShape(RoundedRectangle(cornerRadius: 10))
                        .opacity(index == tabIndex ? 1 : 0.2)
                        .onTapGesture {
                            tabIndex = index
                        }
                }
            }
            .padding()
            .background(.gray)
        }
        .scrollIndicators(.hidden)
    }
}
  • emojiView는 실제 이모지들이 보이는 뷰다.
  • 각각의 이모지 패키지를 TabView 안에 넣어, 가로 스와이프로 탐색할수 있도록 했다.
  • 각 이모지 패키지 탭은 세로 스크롤 안의 그리드 형태로 표시되고 있다.
extension EmojiKeyboardView {
    var emojiView: some View {
        TabView(selection: $tabIndex) {
            ForEach(data, id: \.element) { index, package in
                ScrollView {
                    LazyVGrid(columns: [gridItem], spacing: 20) {
                        ForEach(package.emojis) { emoji in
                            emoji.sample
                                .frame(width: 50, height: 50)
                                .clipShape(RoundedRectangle(cornerRadius: 15))
                        }
                    }
                    .padding(AppPadding.md)
                }
                .tag(index)
            }
        }
        .tabViewStyle(.page(indexDisplayMode: .never))
        .scrollIndicators(.hidden)
        .ignoresSafeArea()
    }
}