- SwiftUI에서 이모지 키보드를 만들어봤다.
- 카톡이나 메신저 앱에서 많이 사용되는 UI다.
이모지 데이터
- 데이터를 위한 구조체 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()
}
}