尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证섬타임: 서버시간 · PiP 시계 (앱스토어 배포)
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗| 프로필 | 이름 | 역할 | 담당 업무 | GitHub |
|---|---|---|---|---|
| 허성필 | PM, iOS | 프로젝트 매니징, Live Activity · PiP 시계 구현, 서버 시간 측정, 설정 기능, UI 컴포넌트 개발 | 🔗 |
| 분류 | 기술 명세 |
|---|---|
| Architecture | TCA (The Composable Architecture) |
| UI Framework | SwiftUI, WidgetKit |
| Live Activity | ActivityKit (Dynamic Island / Lock Screen Live Activity) |
| Picture in Picture | AVKit (AVPictureInPictureController), AVFoundation (AVSampleBufferDisplayLayer, AVAudioSession), CoreMedia, CoreVideo |
| Async / State | Swift Concurrency (async/await, AsyncStream), TCA Dependencies, Swift Sharing (@Shared) |
| Network | URLSession (HTTP Date 헤더 기반 서버 시각 측정) |
| Storage | App Group UserDefaults, App Group Container (File Storage) |
| System | WebKit(WebView), MessageUI(Mail), OSLog |
| Font | Pretendard, Doto (위젯 확장은 서브셋 폰트 사용) |
| Localization | String Catalog (.xcstrings) — 한국어 / 영어 |
| Deployment | iOS 18.0+, iPhone / iPad, Swift 5.0 |
| Ads | Google Mobile Ads SDK (AdMob), User Messaging Platform (UMP) |
HomeFeature 리듀서로 모아, 서버 시간 시계와 일반 시계가 동시에 켜지지 않는 규칙을 한 곳에서 보장했습니다.Reducer로 분리하고 @Reducer enum Destination과 ifLet으로 화면 전환을 조합했습니다. 서버 시간 화면은 delegate 액션으로 시작 · 종료만 요청하고 실제 시계 제어는 홈이 맡아 책임을 나눴습니다.LiveActivityClient · PipClockClient · ServerTimeClient로 정의하고 @Dependency로 주입해, ActivityKit · AVKit · 네트워크 같은 시스템 의존성을 리듀서 밖으로 분리했습니다.@DependencyClient로 클라이언트를 추상화해, 테스트와 프리뷰 환경에서 손쉽게 대체할 수 있도록 설계했습니다.@Shared(.appStorage)로 App Group UserDefaults에 저장해, 설정 화면에서 값을 바꾸는 즉시 홈 · Live Activity · PiP가 같은 값을 바라보도록 했습니다.fileStorage로 App Group 컨테이너에, 실행 중인 서버 시간 시계는 inMemory로 관리해 데이터의 수명에 맞게 저장 위치를 나눴습니다.Text(.currentDate, format:)로 시스템이 직접 시각을 갱신하게 했습니다. 앱은 설정이 바뀔 때만 Live Activity를 업데이트합니다.Text(timerInterval:) 방식은 자정이 지나면 24:00:xx로 넘어가는 문제가 검증 과정에서 확인되어, 최소 지원 버전을 iOS 18.0으로 올렸습니다.Date.FormatStyle은 기기의 24시간제 설정에 따라 표시가 달라져, Date.VerbatimFormatStyle로 형식을 직접 지정했습니다. 한글 글자가 없는 폰트(모노 · 도트)의 12시간제는 "오후" 대신 AM/PM으로 표시합니다.isDynamicIslandLimitedInWidth 환경 값으로 분기해, 폭이 제한된 가로 아일랜드에서는 카메라 위에 시 · 분, 아래에 초를 나눠 그리고 ViewThatFits로 글자 크기를 단계적으로 맞췄습니다.ImageRenderer로 그려 CVPixelBuffer → CMSampleBuffer로 변환한 뒤 AVSampleBufferDisplayLayer에 넣어 AVPictureInPictureController로 띄웠습니다. 창을 키워도 선명하도록 6배 해상도로 그리고, 픽셀 버퍼 풀을 재사용합니다..playback + .mixWithOthers로 설정해 다른 앱의 소리를 끊지 않으며, canStartPictureInPictureAutomaticallyFromInline으로 앱이 백그라운드로 갈 때 PiP가 자동으로 시작되도록 했습니다.HEAD(막히면 GET) 요청을 보내 응답의 Date 헤더로 서버 시각을 얻습니다. 본문은 받지 않고 헤더가 도착하는 즉시 요청을 끊어 왕복 시간을 줄였습니다.Date 헤더는 초 단위라, 응답 순간의 서버 시각이 [S, S+1) 범위에 있다는 점을 이용했습니다. 네트워크 지연을 왕복 시간의 절반으로 보정한 여러 표본의 범위를 교집합으로 좁혀, 폰 시각 대비 오프셋과 오차(±) 를 계산합니다.폰 시각 + 오프셋을 사용합니다. PiP는 밀리초까지 그대로 더하고, Live Activity는 시스템 갱신 텍스트에 오프셋만큼 옮긴 시간대(TimeZone)를 적용했습니다.@Shared(.adConsent) 하나에 저장해 모든 화면의 배너와 설정 화면이 같은 값을 읽습니다.safeAreaInset으로 화면 하단에 고정하고, 동의 확인 · 로딩 중에는 배너 높이만큼 자리를 비워 두며, 광고를 받지 못하면 자리를 접어 레이아웃이 흔들리지 않게 했습니다.| Version | Date | Description |
|---|---|---|
| v26.0.0 | 2026.10.10 | 첫 공식 배포 (Initial Release) |
| v26.1.0 | 2026.10.15 | 티켓팅 연습 기능 추가 (정각 클릭, 포도알 클릭) |
| v26.2.0 | 2026.10.20 | 애드몹(AdMob) 광고 배너 추가, 일본어 지원 |
| v26.3.0 | 2026.10.22 | 목표 시각 카운트다운 · 알림 · 진동 기능 추가, PiP 시계 깜빡임 추가 |
| v26.3.1 | 2026.10.26 | 보안 문자 연습 기능 추가, 목표 시간 10초 후 서버 시계 자동 꺼짐 기능 추가 |
SomTime/
├── SomTime/ # 앱 메인 타깃
│ ├── SomTimeApp.swift # 앱 진입점
│ ├── Info.plist # 앱 Info.plist (폰트, 백그라운드 오디오 모드, AdMob 앱 ID · SKAdNetwork)
│ ├── InfoPlist.xcstrings # 앱 이름 다국어 리소스 (섬타임 / SomTime)
│ ├── Localizable.xcstrings # 다국어 문자열 리소스 (한국어 / 영어 / 일본어)
│ ├── SomTime.entitlements # App Group 권한 (앱↔위젯 데이터 공유)
│ │
│ ├── Features/ # 기능별 화면 모듈 (View · Feature)
│ │ ├── Home/ # 홈 (시계 토글 · 미리보기, 시계 전환의 중심)
│ │ │ ├── HomeFeature.swift
│ │ │ ├── HomeView.swift
│ │ │ └── HomePreviewArea.swift # └ 아일랜드 · PiP 창 미리보기
│ │ ├── ServerTime/ # 서버 시간 (측정 · 표시 방식 선택 · 시작)
│ │ │ ├── ServerTimeFeature.swift
│ │ │ ├── ServerTimeView.swift
│ │ │ └── ServerTimeModels.swift
│ │ ├── Settings/ # 설정 (디자인 · 색상 · 표시 옵션 · 문의하기 · 광고 개인정보 설정)
│ │ │ ├── SettingsFeature.swift
│ │ │ ├── SettingsView.swift
│ │ │ └── MailComposeView.swift
│ │ ├── TicketingPractice/ # 티켓팅 연습 (정각 클릭 · 포도알 클릭)
│ │ │ ├── TicketingPracticeFeature.swift
│ │ │ ├── TicketingPracticeView.swift # └ 연습 선택 화면
│ │ │ ├── TimingPracticeFeature.swift
│ │ │ ├── TimingPracticeView.swift # └ 정각 클릭 연습
│ │ │ ├── SeatPracticeFeature.swift
│ │ │ ├── SeatPracticeView.swift # └ 포도알 클릭 연습
│ │ │ └── TicketingPracticeModels.swift
│ │ └── PrivacyPolicy/ # 개인정보 처리방침
│ │ ├── PrivacyPolicyFeature.swift
│ │ └── PrivacyPolicyView.swift
│ │
│ ├── Clients/ # TCA Dependency Client
│ │ ├── LiveActivityClient.swift # Live Activity 시작 · 업데이트 · 종료 (ActivityKit)
│ │ ├── PipClockClient.swift # PiP 시계 시작 · 업데이트 · 종료
│ │ ├── ServerTimeClient.swift # 서버 시각 측정 (HTTP Date 헤더)
│ │ └── AdClient.swift # 광고 동의 확인 · SDK 시작 · 광고 개인정보 설정
│ │
│ ├── Ads/ # AdMob 배너 광고
│ │ ├── AdController.swift # UMP 동의 처리 · Google Mobile Ads SDK 시작
│ │ ├── AdPlacement.swift # 화면별 광고 단위 ID (Debug는 테스트 광고)
│ │ └── BannerAdView.swift # SDK BannerView를 감싼 UIViewRepresentable
│ │
│ ├── PictureInPicture/ # PiP 시계 구현
│ │ ├── PipClockController.swift # AVPictureInPictureController · 오디오 세션 관리
│ │ └── PipClockFrameRenderer.swift # SwiftUI 시계 → CMSampleBuffer 렌더링
│ │
│ ├── Components/ # 공통 UI 컴포넌트
│ │ ├── AdBannerSlot.swift # 화면 하단 광고 배너 자리
│ │ ├── Chip.swift
│ │ ├── ColorSwatch.swift
│ │ ├── DesignCard.swift
│ │ ├── DifficultyButton.swift
│ │ ├── FeatureCard.swift
│ │ ├── HomeTile.swift
│ │ ├── IconButton.swift
│ │ ├── IconTile.swift
│ │ ├── IslandPill.swift
│ │ ├── MiniSeatGrid.swift
│ │ ├── PipClockWindow.swift
│ │ ├── PracticeClockText.swift
│ │ ├── PracticeModeCard.swift
│ │ ├── PrimaryButton.swift
│ │ ├── RecordList.swift
│ │ ├── ResultIcon.swift
│ │ ├── ScreenHeader.swift
│ │ ├── SeatButton.swift
│ │ ├── SegmentedPicker.swift
│ │ ├── SelectionButton.swift
│ │ ├── SwitchIndicator.swift
│ │ ├── TimeBreakdownBar.swift
│ │ ├── ToastMessage.swift
│ │ ├── URLInputField.swift
│ │ └── WebView.swift
│ │
│ ├── Storage/ # @Shared 저장 키
│ │ ├── AdSharedKeys.swift # 광고 동의 상태 (inMemory)
│ │ ├── ClockSettingsSharedKeys.swift # 시계 설정 (App Group UserDefaults)
│ │ ├── ServerTimeSharedKeys.swift # 서버 시간 입력값 · 측정 결과 · 실행 상태
│ │ └── TicketingPracticeSharedKeys.swift # 티켓팅 연습 기록 · 옵션 (앱 전용 저장소)
│ │
│ ├── Extensions/ # 디자인 · 색상 · PiP 비율 · 난이도 표시 이름
│ │ ├── ClockColor+Name.swift
│ │ ├── ClockDesign+Name.swift
│ │ ├── PipAspectRatio+Name.swift
│ │ └── TicketingPractice+Name.swift
│ │
│ ├── Utilities/
│ │ ├── DeviceCapability.swift # 다이나믹 아일랜드 · PiP 지원 여부 판별
│ │ └── UINavigationController+SwipeBack.swift # 스와이프 백 제스처
│ │
│ ├── Resources/ # 개인정보 처리방침 HTML (ko / en / ja)
│ └── Assets.xcassets/ # 앱 아이콘
│
├── Shared/ # 앱 · 위젯 확장 공유 코드
│ ├── ClockActivityAttributes.swift # Live Activity 공유 Attributes
│ ├── ClockDisplaySettings.swift # 시계 표시 설정 묶음 (서버 시간 오프셋 포함)
│ ├── ClockSettingsStorage.swift # App Group UserDefaults 키 · 기본값
│ ├── ClockTimeFormatter.swift # 시각 문자열 형식 (VerbatimFormatStyle)
│ ├── ClockDesign.swift # 시계 디자인 (기본 · 굵게 · 모노 · 도트)
│ ├── ClockColor.swift # 시계 색상 6종 · 강조색
│ ├── PipAspectRatio.swift # PiP 창 비율 (10:1 · 2:1)
│ ├── ServerTimeClock.swift # 서버 시간 시계 정보 (호스트 · 오프셋)
│ ├── DesignMetrics.swift # 디자인 수치
│ ├── FontStyle.swift # 폰트 스타일 정의
│ ├── SharedAssets.xcassets/ # 디자인 토큰 · 시계 색상 에셋
│ └── Fonts/ # Pretendard, Doto
│
└── SomTimeWidget/ # 위젯 익스텐션 타깃 (다이나믹 아일랜드 / Live Activity)
├── SomTimeWidgetBundle.swift # └ 위젯 번들 진입점
├── ClockLiveActivity.swift # 잠금 화면 · 다이나믹 아일랜드 UI
├── IslandClockViews.swift # 컴팩트 · 미니멀 · 가로 모드 시계 뷰
├── LiveClockText.swift # 시스템이 갱신하는 현재 시각 텍스트
├── Fonts/ # Live Activity 용량 제한용 Pretendard 서브셋
├── Localizable.xcstrings # 위젯 다국어 문자열
├── SomTimeWidgetExtension.entitlements # 위젯 App Group 권한
└── Info.plist # 위젯 익스텐션 Info.plist
Copyright © 2026 Seongpil Heo. All rights reserved.