Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/developeracademy-postech/2024-nc2-m20-screentime
https://github.com/developeracademy-postech/2024-nc2-m20-screentime
Last synced: about 13 hours ago
JSON representation
- Host: GitHub
- URL: https://github.com/developeracademy-postech/2024-nc2-m20-screentime
- Owner: DeveloperAcademy-POSTECH
- Created: 2024-06-17T08:37:59.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2024-06-20T10:36:09.000Z (5 months ago)
- Last Synced: 2024-06-21T12:23:25.087Z (5 months ago)
- Language: Swift
- Size: 3.59 MB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# 2024-NC2-M20-ScreenTime
2024 Apple Deveopler Academy @ POSTECH, Morning Session AengZi & TEO
๐ 2024.06.10(Mon) - 2024.06.21(Fri)
๐ฒ ios 17.4/SwiftUI/## ๐ฅ Youtube Link
(์ถํ ๋ง๋ค์ด์ง ์ ํ๋ธ ๋งํฌ ์ถ๊ฐ)## โณ About Screen Time
![ScreenTime](https://github.com/DeveloperAcademy-POSTECH/2024-NC2-M20-ScreenTime/blob/main/ScreenTime.png)> ๋์งํธ ๊ธฐ๊ธฐ์ ์ฌ์ฉ ์๊ฐ์ ์ถ์ ํ๊ณ ๊ด๋ฆฌํ ์ ์๋ ๊ธฐ๋ฅ
>**Screen Time**์ ์น ์ฌ์ฉ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ๋ฐ ์ถ์ , ๊ด๋ฆฌํ๋ ๊ฒ๊ณผ ๊ด๋ จ๋ ๊ธฐ์ ์ ๋๋ค.
$\color{#5c56d2} \bf ๊ธฐ๊ธฐ$ $\color{#5c56d2} \bf ์ฌ์ฉ์๊ฐ$ $\color{#5c56d2} \bf ์ถ์ $ DeviceActivity
๊ธฐ๊ธฐ ์ฌ์ฉ์๊ฐ, ์ฑ ์๋น์ค, ์น ์ฌ์ดํธ์ ๋จธ๋ฌธ ์๊ฐ์ ์ถ์ ํ๊ณ ๊ธฐ๋ก, ๋ถ์ํ ์ ์์ต๋๋ค.
$\color{#5c56d2} \bf ์คํฌ๋ฆฐํ์$ $\color{#5c56d2} \bf ์ ์ด$ Managed Settings
๊ธฐ๊ธฐ ์ฌ์ฉ ์๊ฐ์ ์ถ์ ํด์ ํน์ ์ดํ์ด๋ ์น ์ฌ์ดํธ์ ๋จธ๋ฌด๋ฅด๋ ์๊ฐ์ ์ ์ดํ ์ ์์ต๋๋ค.
$\color{#5c56d2} \bf ์คํฌ๋ฆฐํ์$ $\color{#5c56d2} \bf ์ฌ์ฉ๋$ $\color{#5c56d2} \bf ๊ณต์ $ FamilyControls
๋ณดํธ์๊ฐ ์๋ ์ ์คํฌ๋ฆฐ ํ์์ ๊ณต์ ๋ฐ์ ์ ์๊ณ ๊ทธ๊ฒ์ ๋ณดํธ์ฐจ์์์ ์ ํํ ์ ์์ต๋๋ค.
## ๐ฏ What we focus on?
### **๐ย ์ฐ๋ฆฌ๊ฐ Screen Time์์ ์ง์คํ โ์คํฌ๋ฆฐํ์ ์ ์ดโ**(Managed Settings)**1. ๋ค์ดํ์**
ํน์ ์๊ฐ์ ์ ํด์ ๊ทธ ์๊ฐ๋์๋ ๊ธฐ๋ณธ์ ์ธ ์ฑ์ธ ์ค์ , ๊ฑด๊ฐ, ์ ํ ๋ฑ์ ์ฑ๊ณผ ๋ณ๊ฐ๋ก ์ ํํด ๋ ์ฑ ์ด์ธ์ ์ฌ์ฉ์ ์ ํ
**2. ์ฑ ์๊ฐ ์ ํ**
ํน์ ์ฑ์ ์ผ์ ์๊ฐ ๋์๋ง ์ฌ์ฉํ ์ ์๋๋ก ์ ํ
**3. ์ปจํ ์ธ ๋ฐ ๊ฐ์ธ์ ๋ณด ๋ณดํธ ์ ํ**
์ฑ ์ค์น์ ์ญ์ , ํน์ ์ฌ์ดํธ์ ์ฝํ ์ธ ๋ฅผ ์์ ์ ์ํ ์ ์๋๋ก ์ค์ ํ๋ ๊ธฐ๋ฅ
## ๐ผ Use Case
- ์ง์ค๋ชจ๋(Focus)์์๋ ์ ์ ์ ์ ์ ์ ์ํฉ์ ๋ง๊ฒ ์ฑ ์ปค๋ฎค๋์ผ์ด์ ์ ํ์ ํ ์ ์๋ ๊ธฐ๋ฅ์ด ์์ง๋ง ์๊ฐ ์ค์ ์ด ์ด๋ ต๋ค!
- ์คํฌ๋ฆฐํ์(Screen Time)์์๋ ์ฑ ์ ํ ๋ฐ ์๊ฐ ์ค์ ์ด ๊ฐ๋ฅํ์ง๋ง ์ ์ ์ ์ ์ ์ ์ํฉ์ ๋ง๊ฒ ์ค์ ํ ์ ์๋ค!โ Screen Time์ ์ฑ ์ ๊ทผ/์ฌ์ฉ์ ๊ด๋ฆฌํ๋ ManagedSettings ํ๋ ์์ํฌ: ๋ค์ดํ์ ๊ธฐ๋ฅ์ ํ์ฉํ์ฌ ์ฌ์ฉ์์๊ฒ ์๋ง๋ ๋ค์ดํ์ ์ง์ค๋ชจ๋๋ฅผ ๊ฐ๋ฐํด๋ณด์!
### โ ์ฑ ์ฌ์ฉ ์๊ฐ์ ์ ํํจ์ผ๋ก ์ ๋ฌด์ ๋ํ ์ง์ค์ ๋์์ฃผ๋ $\color{#5c56d2} \bf ๋ฝ๋ชจ๋๋ก$ $\color{#5c56d2} \bf ํ์ด๋จธ$ ๋ฅผ ๋ง๋ค์ด๋ณด์!
> ๋ฝ๋ชจ๋๋ก ๊ธฐ๋ฒ(Pomodoro Technique)์ด๋? ์ง์ค๋ ฅ ํฅ์์ด ๋ชฉ์ ์ธ ์๊ฐ ๊ด๋ฆฌ ๋ฐฉ๋ฒ๋ก ์ผ๋ก, **25๋ถ ๋์ ๋ฌด์ธ๊ฐ์ ์ง์คํ๊ณ 5๋ถ ๋์ ์ฌ๋ ๊ฒ์ 4๋ฒ ๋ฐ๋ณตํ๊ณ , ๊ทธ ๋ค์ 30๋ถ๊ฐ ์ฌ๋๋ก ์๊ฐ์ ๋ฐฐ๋ถ**ํ๋ ๋ฐฉ์์ผ๋ก ์ด๋ฃจ์ด์ง๋ค. ์ด ๋, "25๋ถ ์ง์ค + 5๋ถ ํด์"์ ์ฌ์ดํด์ ๊ฐ๋ฆฌ์ผ โ๋ฝ๋ชจ๋๋กโ๋ผ๊ณ ํ๊ณ , ์ด ์ฌ์ดํด์ 1ํ ์๋ฃํ๋ ๊ฒ์ "1 ๋ฝ๋ชจ๋๋ก" ๋ผ๊ณ ํ๋ค.## ๐ผ๏ธ Prototype
![prototype](https://github.com/DeveloperAcademy-POSTECH/2024-NC2-M20-ScreenTime/blob/main/prototype.gif)
* ์คํ ๊ณผ์ ์ ๋น ๋ฅด๊ฒ ๋ณด์ฌ๋๋ฆฌ๊ธฐ ์ํด, ์์๋ก ํ์ด๋จธ์ ์๊ฐ์ 25๋ถ๊ณผ 5๋ถ โ ๋ชจ๋ 2์ด๋ก ์กฐ์ ํ์์ต๋๋ค.## ๐ ๏ธ About Code
### 1. ManagedSetting ๊ถํ ์์ฒญ```swift
import SwiftUI
import FamilyControls@main
struct ScreenTimeApp: App {
let center = AuthorizationCenter.shared
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
Task {
do {
try await center.requestAuthorization(for: .individual)
} catch {
print("Fail: \(error)")
}
}
}
}
}
}
```์ํธ๋ฆฌ ํฌ์ธํธ์ธ App ๊ตฌ์กฐ์ฒด์ FamilyControls์ ๋ํ ๊ถํ์ ์์ฒญ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
iOS15์์๋ ๊ฐ์กฑ ๊ตฌ์ฑ์ ์ค ๋ถ๋ชจ ๊ถํ์ ์น์ธ์ด ์์ด์ผ ์คํ๊ฐ๋ฅ ํ์ง๋ง, iOS16 ๋ฒ์ ๋ถํฐ ๊ฐ์กฑ ๊ตฌ์ฑ์์ ์น์ธ ์์ด ์์ ์ iPhone์ ์ค์ค๋ก ๊ถํ์ ์์ฒญํ ์ ์๋๋ก ์ ๋ฐ์ดํธ ๋์์ต๋๋ค (.individual ์ ํ)
### 2. ManagedSetting์ ๊ด๋ฆฌํ๋ ๊ฐ์ฒด
```swift
import Foundation
import FamilyControls
import ManagedSettingsclass ManagedSettingModel: ObservableObject {
static var shared = ManagedSettingModel()
let store = ManagedSettingsStore()
@Published var selectionToDiscourage: FamilyActivitySelection
init() {
selectionToDiscourage = FamilyActivitySelection()
}
...
}
```๊ถํ์ ์ ์ดํ๋ ๊ฐ์ฒด๋ฅผ ๋จ ํ๋๋ง ๋๊ธฐ ์ํด SingleTonํจํด์ผ๋ก ManagedSetting๋ชจ๋ธ์ ๋ง๋ค์์ต๋๋ค.
์ฑ๊ธํค ํจํด์ ํ๋์ ์ดํ๋ฆฌ์ผ์ด์ ๋ด์์ ํ๋์ ์ธ์คํด์ค(๊ฐ์ฒด)๋ง ์ฌ์ฉํด์ผํ๋ ์ํฉ์ ๊ตฌํํฉ๋๋ค.
์ดํ๋ฆฌ์ผ์ด์ ์ ์ฒด ์์ญ์์ ๋์ผํ ๊ฐ์ฒด์ ์ ๊ทผํ ์ ์๋ ์ฅ์ ๋๋ถ์ ์ ์ ์ ๋ณด, ์ฑ ์ค์ ๊ณผ ๊ฐ์ด, ์ธ์คํด์ค๋ค์ด ๊ผฌ์ด๋ ์ํฉ์ ๋ฐฉ์งํ๊ณ , ๋ฉ๋ชจ๋ฆฌ์ ๋ญ๋น๋ฅผ ๋ง์ ์ ์์ต๋๋ค.
ํ์ฌ ์ํฉ์์๋ ํนํ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ ํ๊ณผ ํด์ ๋ฅผ ์ ์ดํ๋ ๊ฐ์ฒด๋ ๋จ ํ๋๋ง ์กด์ฌํด์ผ ๊ผฌ์ด์ง ์๊ณ , ์๋์น ์์ ์ดํ ์ ํ/ํด์ ๋ฅผ ๋ฐฉ์งํ ์ ์์ด์ SingleTonํจํด์ ์ ์ฉํ์์ต๋๋ค.
๋ค๋ฅธ ํ์ผ์์๋
`ManagedSettingModel.shared.[ ๋ฉ์๋ ํน์ ํ๋กํผํฐ ]` ์ฝ๋๋ก ์ ๊ทผํ ์ ์์ต๋๋ค
### 3. ์ฑ ์ ํ ๋ฐ ํด์ ๊ธฐ๋ฅ
```swift
import Foundation
import FamilyControls
import ManagedSettingsclass ManagedSettingModel: ObservableObject {
...
func setShieldRestrictions() {
let applications = ManagedSettingModel.shared.selectionToDiscourage
storeshield.applications = applications.applicationTokens.isEmpty ? nil : applications.applicationTokens
store.shield.applicationCategories = applications.categoryTokens.isEmpty ? nil : ShieldSettings.ActivityCategoryPolicy.specific(applications.categoryTokens)
}
func freeShieldRestrictions() {
store.shield.applications = nil
store.shield.applicationCategories = nil
}
func isSelectionEmpty() -> Bool {
selectionToDiscourage.applicationTokens.isEmpty &&
selectionToDiscourage.categoryTokens.isEmpty &&
selectionToDiscourage.webDomainTokens.isEmpty
}
}
```์ดํ๋ฆฌ์ผ์ด์ ์ ์ ํ, ํด์ ํ๊ธฐ ์ํด์ `ManagedSettingsStore()` ์ `sheild` ์ `applications` ์ ์ ํํ ์ดํ์ ํ ํฐ๊ฐ์ ์ถ๊ฐํ๊ฑฐ๋ ๋น์์ฃผ์ด์ผ ํฉ๋๋ค.
์ ์ฝ๋์ ๊ฒฝ์ฐ
`setShieldRestrictions()` ๋ฉ์๋ ์ ์ธํ๊ณ ์ดํ๋ฆฌ์ผ์ด์ ํ ํฐ์ ๋ด์ ์ ์๊ฒ ํ์๊ณ
`freeShieldRestrictions()` ๋ฉ์๋๋ฅผ ์ ์ธํ๊ณ ์ดํ๋ฆฌ์ผ์ด์ ํ ํฐ์ ๋น์ธ ์ ์๊ฒ ํ์์ต๋๋ค.
์ด์ ํด๋น ๋ฉ์๋๋ค์ ํ์ํ ์์ ์ ์คํ ์์ผ์ฃผ๋ฉด ์ฑ์ ์ ํํ๊ฑฐ๋ ํด์ ํ ์ ์์ต๋๋ค!
### 4. ์ ํ ํ ์ฑ์ ์ ํํ๋ ๋ฐฉ๋ฒ
```swift
import SwiftUI
import FamilyControlsstruct RoutinePrepareView: View {
@EnvironmentObject var managedSettingModel: ManagedSettingModel
@State var isPresented = false
var body: some View {
VStack { ... }
}
.familyActivityPicker(isPresented: $isPresented,
selection: $managedSettingModel.selectionToDiscourage)}
}
```View๋ฅผ ๊ทธ๋ฆด ๋, ์ต์์ ๋ทฐ์ ์๋ ์์ ์๋ฅผ ์์ฑํ๊ณ
์ฒซ ๋ฒ์งธ ์ธ์๋ก๋ true๋ก ๋ฐ๋ ์ ์๋ ์ํ๊ฐ
๋ ๋ฒ์งธ ์ธ์๋ก๋ ์ ํํ ์ฑ์ ํ ํฐ์ ๋ด์ ์ ์๋ ์ํ๊ฐ
์ ๋์ ํ๋ฉด ๋ฏธ๋ฆฌ ๊ตฌํ๋, ์ ํ ํ ์ฑ์ ์ ํํ๋ Modal๋ทฐ๊ฐ ๋ํ๋ฉ๋๋ค.
```swift
.familyActivityPicker(isPresented: Binding,
selection: Binding)
```๊ทธ๋ฆฌ๊ณ ๊ทธ Modal ๋ทฐ์์ ์ฑ์ ์ ํํ๋ฉด ์ฑ์ ํ ํฐ๋ค์ด ๋ด๊ธฐ๊ฒ ๋๊ณ
ํธ๋ฆฌ๊ฑฐ๊ฐ ๋๋ ๋ฒํผ์์`setShieldRestrictions()` ์คํํ๋ฉด, ์ ํํ ์ดํ๋ค์ ์ฌ์ฉ์ด ์ ํ ๋ฉ๋๋ค.