개인용 iPhone·iPad RSS 리더. Swift 6 + SwiftUI + SwiftData, iOS 18 이상.
저장소: https://github.com/jeongsk/rss-mobile-app
사이트 주소(https://news.ycombinator.com/)만 넣어 피드를 찾고, 글을 열면 원문에서 본문을 추출해 리더 화면으로 보여 준다.
| 기능 | 내용 |
|---|---|
| Feedly 가져오기 | Feedly에서 내보낸 OPML 파일을 폴더 구조 그대로 가져온다. OPML 내보내기도 지원 |
| 피드 추가 | 블로그 주소만 넣어도 RSS/Atom 주소를 찾아 준다 |
| 목록 레이아웃 | 리스트, 매거진, 그리드, 제목만 (폴더·피드마다 따로 기억) |
| 오프라인 전체 본문 | 요약만 있는 피드는 원문 페이지에서 본문을 추출해 저장. 본문 이미지도 저장 |
| 리더 ↔ 원본 웹 | 리더 화면 오른쪽 위 나침반 버튼. 원본 웹 하단의 **"이 페이지를 리더로 저장"**으로 지금 보는 페이지를 오프라인 본문으로 저장 (로그인·브라우저 확인이 필요한 사이트용) |
| 소개 사이트 | GeekNews는 원문 기사 링크와 댓글(Hacker News 의견 포함)을 함께 표시 |
| 읽기 스타일 | 테마 5종, 글자 크기, 줄·문단 간격, 여백, 양쪽 정렬, 사용자 폰트(TTF·OTF) |
| 번역 | Apple 기기 내 번역(무료, 오프라인 가능). 원문만 / 원문+번역 / 번역만 |
| 하이라이트·메모 | 글자를 길게 눌러 선택 → 하이라이트 또는 메모. 모아보기, Markdown 내보내기 |
| 읽어주기(TTS) | 문장 표시, 속도·음성 선택, 번역문 읽기, 읽지 않은 글 연속 듣기, 잠금 화면 제어 |
| 기기 간 동기화 | iCloud Drive의 폴더 하나로 아이폰·아이패드 동기화 |
| 백그라운드 갱신 | 약 30분 간격 새 글 받기, 쉬는 시간에 남은 본문 저장 |
- 준비
brew install xcodegen git clone https://github.com/jeongsk/rss-mobile-app.git cd rss-mobile-app xcodegen generate open RSSReader.xcodeproj - Xcode > Settings > Accounts에서 Apple ID를 추가한다.
project.yml맨 위settings.base의APP_BUNDLE_ID를 나만의 값으로 바꾼다. 앱, 위젯(<번들 ID>.Widget), App Group(group.<번들 ID>)이 모두 이 값에서 만들어진다.팀 ID는 저장소에 넣지 않고 환경변수APP_BUNDLE_ID: com.<내이름>.RSSReader
DEVELOPMENT_TEAM으로 준다. Xcode > Signing & Capabilities에서 Personal Team을 고르면 보이는 값이다.(Xcode에서 직접 바꿔도 되지만echo 'export DEVELOPMENT_TEAM=<팀 ID>' >> ~/.zshrc # 한 번만 source ~/.zshrc && xcodegen generate
xcodegen generate를 다시 실행하면 사라진다.build.sh,test.sh도 매번 다시 생성한다.)- 아이폰을 연결하고 아이폰의 설정 > 개인정보 보호 및 보안 > 개발자 모드를 켠다.
- Xcode에서 실행 대상을 내 아이폰으로 고르고 ▶︎ 실행.
- 처음 실행 시 아이폰 설정 > 일반 > VPN 및 기기 관리에서 내 개발자 인증서를 신뢰한다.
무료 계정 제약: 설치한 앱은 7일 뒤 실행되지 않는다. Xcode에서 다시 ▶︎ 실행하면 데이터는 그대로 두고 다시 7일간 쓸 수 있다. 무료 계정은 iCloud(CloudKit) 기능을 쓸 수 없어서, 동기화는 iCloud Drive 폴더 방식으로 구현했다.
- Feedly 구독 옮기기: 사이드바
+> Feedly / OPML 가져오기 > "Feedly OPML 내보내기 열기"에서 파일을 받은 뒤 그 파일을 선택한다. - 동기화: 설정 > iCloud Drive 동기화 폴더 선택 > iCloud Drive에
내 RSS 동기화같은 폴더를 만들어 선택한다. 아이패드에서도 같은 폴더를 선택한다. 두 번째 기기는 OPML을 다시 가져오지 말고 폴더만 선택하면 구독이 넘어온다. - 번역 언어: 처음 번역할 때 언어 모델 다운로드 창이 뜬다. 설정 > 앱 > 번역에서 미리 받아 둘 수도 있다.
- 고품질 음성: 설정 > 손쉬운 사용 > 읽기 및 말하기 콘텐츠 > 음성에서 한국어 '고품질' 음성을 받으면 앱 설정의 음성 목록에 나타난다.
시뮬레이터에서 확인할 수 없어서 기기에서 직접 봐야 하는 항목이다.
- 번역 (Apple 번역은 시뮬레이터 미지원) — 원문+번역, 번역만, 자동 번역
- iCloud Drive 폴더를 통한 아이폰 ↔ 아이패드 동기화
- 백그라운드 갱신 (iOS가 실행 시점을 정한다. 앱을 자주 쓸수록 자주 실행된다)
- TTS 백그라운드 재생, 잠금 화면·이어폰 제어, 고품질 음성
- 파일 앱에서 폰트 파일 추가
- Feedly에서 받은
.opml파일이 파일 선택 창(다운로드, iCloud Drive)에서 선택되는지 - 7일 뒤 앱이 열리지 않으면 Xcode에서 다시 실행
- GeekNews처럼 브라우저 확인을 요구하는 사이트: 원본 웹에서 확인을 통과한 뒤 "이 페이지를 리더로 저장"이 되는지
./build.sh # 시뮬레이터 빌드
./test.sh # 단위 테스트 + UI 테스트
./test.sh -only-testing:RSSReaderTests
./run.sh "iPhone 17" -demo # 샘플 구독을 넣고 실행 (DEBUG)
TEST_RUNNER_LIVE_TESTS=1 ./test.sh -only-testing:RSSReaderTests/LiveExtractionTests # 실제 사이트 본문 추출 점검
TEST_RUNNER_DEMO_VIDEO=1 ./test.sh -only-testing:RSSReaderUITests/DemoUITests # 데모 흐름 (녹화: xcrun simctl io booted recordVideo demo.mp4)DEBUG 실행 인자 (캡처·점검용): -demo, -openFirst, -layout list|magazine|grid|titles, -sidebar, -highlights, -sheet addFeed|importOPML|settings, -theme, -font, -web, -style, -speak, -fakeTranslate -display both|translated, -demoHighlight, -syncFolder <경로>.
| 경로 | 내용 |
|---|---|
RSSReader/Models/Models.swift |
SwiftData 모델 (Folder, Feed, Article, Highlight, Tombstone) |
RSSReader/Services/FeedParser.swift, OPML.swift |
RSS 2.0 / RSS 1.0 / Atom / OPML 파서 |
RSSReader/Services/ContentExtractor.swift |
Readability 방식 본문 추출 (SwiftSoup) |
RSSReader/Services/FeedRefresher.swift |
갱신·오프라인 캐시 (ModelActor, 작업마다 새 컨텍스트) |
RSSReader/Services/SyncEngine.swift, SyncManager.swift |
iCloud Drive 폴더 동기화 |
RSSReader/Services/SpeechPlayer.swift |
TTS |
RSSReader/Resources/reader.js |
리더 화면 하이라이트·TTS 문장 표시 (앱 전용 content world) |
DB 쓰기는 모두 메인 컨텍스트 하나에서 한다. SwiftData는 저장할 때 객체의 모든 속성을 쓰기 때문에, 두 컨텍스트가 같은 글을 고치면 나중에 저장한 쪽이 다른 쪽의 변경(별표·읽음 또는 본문)을 지운다. 네트워크, 본문 추출, 파일 입출력만 메인 스레드 밖에서 한다.
동기화 폴더에 기기마다 devices/<기기 ID>.json 하나씩, 폰트는 fonts/에 둔다. 각 기기는 자기 파일만 쓰고 다른 기기 파일을 읽어 병합한다. 같은 대상이면 updatedAt이 최신인 쪽이 이기고, 삭제는 삭제 기록(tombstone)으로 전파한다. 피드는 정규화한 주소, 글은 피드 주소|guid, 하이라이트는 UUID로 기기 간 같은 대상을 알아본다. 별표·하이라이트한 글은 글 정보도 함께 보내서, 상대 기기에 없는 예전 글도 만들어진다(본문은 그 기기가 원문에서 다시 받는다).
MIT. 함께 쓰는 라이브러리와 참고한 코드의 고지는 THIRD_PARTY_NOTICES.md에 있다.