外国人のために、ハングルを最も正しく・楽しく学べる方法 🇰🇷
2人開発 (+ 2人デザイン) (23.09.04 ~ 23.11.24)
📺 アプリ紹介動画のリンク(YouTube)
最近、韓国や韓国語を愛する外国人が本当に多くなっていて、それに伴いハングルの人気も高まっています。 何でも最初に学ぶときは、正しく学ぶことが一番大切だと思います。
Kompassはハングルをハングルらしく教える方法を絶えず模索してきました。
ハングルをアルファベットに変換して教えるのではなく、直接韓国人の発音を聞きながら学びます。
子音は連想記憶法で一生忘れないようにし、形が似ている母音は直接書きながら学ぶようにしました。
ハングルをすべて学んだら、韓国料理や名所を読みながら復習もでき、よく間違える子音と母音は一箇所にまとめて確認することができますハングル(Korean)学習の最強のコンパス、Kompassと一緒にハングルマスターしよう〜!
🛒 App Storeのリンク
![](https://private-user-images.githubusercontent.com/83645833/342356354-350ca266-0cc5-4ca2-af13-24a5883b36b4.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjM5NzEzMzIsIm5iZiI6MTcyMzk3MTAzMiwicGF0aCI6Ii84MzY0NTgzMy8zNDIzNTYzNTQtMzUwY2EyNjYtMGNjNS00Y2EyLWFmMTMtMjRhNTg4M2IzNmI0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MTglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODE4VDA4NTAzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTRhNmM4Y2M5YTZhMTQyZWNkMTI5NWM0OThiOGE1YjBhMGMzNjJiYTM1MzcyOThhYjhiNTQ3OTZiZTE5YTgwZmMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.vW-FIlFxm1b0T01M5cqPq6MRWBBIrQXjFhxai4DkEO0)
0. 技術概要
- SwiftUIベースでの開発
1. オンボーディング・プロローグビュー
- 教育アプリなので、学習前に学習システムを十分に理解できるようにする
- AppStorageを活用して、初めてのユーザーにのみオンボーディングビューを表示
2. 教育タブ(メインタブ)
- 子音・母音・パッチムごとに異なる方式で、最も効率的にハングルを学べるカリキュラムを構成
- アプリ全体の状態管理を効果的に処理するために、
@EnvironmentObject
を使用してEducationManager
を注入し、ユーザー体験を一貫して維持 - 子音教育:LottieAnimationを用いた連想記憶法の活用(例:ㄱ - 🔫)
- 母音教育:直接書きながら学べるように
UIViewRepresentable
を使ってpencilKit
とSwiftUIを接続 - 共通:子音・母音の学習時に必ず該当する音を聞かせ、クイズを解いてから章を終了できるように設定
- UI:
matchedGeometryEffect
と@Namespace
を活用し、App Storeに似た滑らかなUIを実装
3. 練習タブ
- 子音・母音学習後に実際の韓国語単語を通じてハングルを深く学べるようにする
- 韓国の名所・料理・よく使う表現など、外国人が興味を持ちやすい単語で学習
4. コレクションタブ
- カリキュラムのすべての子音・母音をカード形式で一か所に集め、復習できるようにする
- 教育タブでクイズを解くときに間違えたハングルはAppStorageに保存し、それを集めて表示することで、弱点を補強できるようにする
@EnvironmentObject を活用した状態管理
Kompassアプリで教育に関連するすべての属性とメソッドを管理するEducationManager
を宣言。@StateObject
でインスタンスを宣言して管理し、各タブにこれを@EnvironmentObject
として注入。LearnTabのすべてのサブビューはeducationManager
にアクセスして変更を通知できる。
シングルトンパターンの利点を持ちながら、SwiftUIの宣言的プログラミングスタイルを効果的に維持することができる。
struct ContentView: View {
@StateObject var educationManager: EducationManager = EducationManager()
@StateObject var practiceManager: PracticeManager = PracticeManager()
var body: some View {
TabView {
LearningView()
.environmentObject(educationManager)
.tabItem {
Label("Learn", systemImage: "book")
}
PracticeView()
.environmentObject(practiceManager)
.tabItem {
Label("Practice", systemImage: "message.and.waveform.fill")
}
CollectionView()
.environmentObject(educationManager)
.tabItem {
Label("Collection", systemImage: "rectangle.stack.fill")
}
}
}
}
学習コンテンツにおける画面遷移
![](https://private-user-images.githubusercontent.com/83645833/342428022-b620a23d-ec04-4e09-9dc1-3289c3178563.gif?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjM5NzEzMzIsIm5iZiI6MTcyMzk3MTAzMiwicGF0aCI6Ii84MzY0NTgzMy8zNDI0MjgwMjItYjYyMGEyM2QtZWMwNC00ZTA5LTlkYzEtMzI4OWMzMTc4NTYzLmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MTglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODE4VDA4NTAzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWZlOGNjMjQ5MTEwODRmZDJkMTg4NTJlZTQ5ZThiNzZlNjU4M2ZkODZiZGUyM2ZhYWI3ZDBlYjRjYTM5ZGRiZDQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.F_miR4Uyyj5jgzsUAKe2_V-5q_cNAxqVoqA01rY5fDA)
通常の教育コンテンツでは、上の図のように現在のビューが左に消え、右から新しいビューが現れる形式で画面遷移が行われます。しかし、一般的に使用されるほとんどのアプリでは、以下のような方法で画面遷移が行われます。
NavigationView
とNavigationLink
(UINavigationController
)を使用する方法では、アニメーションは似ていますが、ビューがスタックに積み重なるため、ビュー間に階層が生じます。また、階層が深くなるほどメモリ使用量も増加する可能性があります。
.sheet
や.fullScreenCover
を使用すると、モーダル形式でビューが表示されたり、下から上に上がるアニメーションで実装されるため、ユーザーが違和感を感じることがあります。
educationManager
の属性に応じてビューを切り替える際、SwiftUIのtransition
メソッドでasymmetric
を使用しました。asymmetric
メソッドは、ビューのトランジション時に、挿入と消去の際のアニメーションを指定できるため、左に消えて右から現れるビューの切り替えを簡単に実装できました。
struct LearningLessonView: View {
@EnvironmentObject var educationManager: EducationManager
var body: some View {
switch educationManager.currentEducation {
case .learning:
HangulEducationLearningView(progressValue: $progressValue)
.environmentObject(educationManager)
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))
case .recap:
HangulEducationRecapView(progressValue: $progressValue)
.environmentObject(educationManager)
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))
}
}
}
PencilKitの使用のためにUIKitとSwiftUIを接続
母音は形が似ているため、早く慣れるためには実際に書いてみる方法が最も効果的だと判断し、それを実現するためにPencilKitを使用してペンで書く機能を実装しようとしました。ペンを使うビューとしてPKCanvasView
を使用しますが、これはUIScrollView
を継承しています。そのため、SwiftUIベースのUIを使用しているKompassでは、UIKitのビューを使用する必要がありました。
![](https://private-user-images.githubusercontent.com/83645833/342429208-9f714f6b-1eeb-419d-ae46-b8131599a63a.gif?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjM5NzEzMzIsIm5iZiI6MTcyMzk3MTAzMiwicGF0aCI6Ii84MzY0NTgzMy8zNDI0MjkyMDgtOWY3MTRmNmItMWVlYi00MTlkLWFlNDYtYjgxMzE1OTlhNjNhLmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MTglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODE4VDA4NTAzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTJhMTk4OWQ2YTlkZjA3NWJhMjFhZmE5ZGMxZGMwNzBiMjE2YTA4NjkyOGUzODYxN2I3ODc1ZDU5YjQ0ZTFiMmYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.tivgXDaJhu6-8l18xovxtngK0wgBy5da3fhuoqdIGPA)
プロトコルUIViewControllerRepresentable
を使用すれば、UIViewController
をSwiftUIで利用でき、データのやり取りもスムーズに行うことができます。
struct Canvas: UIViewControllerRepresentable {
@Binding var writingCount : Int
@EnvironmentObject var educationManager: EducationManager
func makeUIViewController(context: Context) -> UIViewController {
let fallingViewController = FallingViewController(writingCount: $writingCount, educationManager: educationManager)
return fallingViewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
class FallingViewController : UIViewController, UITextFieldDelegate, PKCanvasViewDelegate, UICollisionBehaviorDelegate {
@Binding var writingCount: Int
var educationManager: EducationManager
// ... [후략] ...
}
Category | Name | Tag |
---|---|---|
Framework | SwiftUI | UI |
PencilKit | Drawing | |
CoreMotion | Motion | |
AvKit | Sound | |
Library | Lottie | LottieAnimation |
オンボーディングビュー | 教育タブ(メインタブ) | 子音学習ビュー | 復習ビュー(リキャップビュー) |
---|---|---|---|
クイズビュー | 母音学習ビュー(PencilKit) | 練習タブ | コレクションタブ |