Ratewise displays current exchange rates for a selected base currency and recalculates them for locale-aware amount input.
The project focuses on modular architecture, strict concurrency, offline resilience, accessibility, localization, automated testing, and reproducible development tooling.
- Current exchange rates from ExchangeRate-API
- Configurable base currency
- Locale-aware amount parsing and formatting
- Searchable currency picker
- English and Turkish localization
- Offline cache with remote-failure fallback
- Pull-to-refresh without discarding existing content
- Light and Dark Modes
- Dynamic Type and accessibility text layouts
- VoiceOver metadata and semantic traits
- Reduce Motion support
- Custom shared-element currency transition
- Unit, accessibility, localization, coordinator, and snapshot tests
- GitHub Actions continuous integration
Ratewise uses a modular MVVM-C architecture with dependency injection.
flowchart TD
App["Ratewise<br/>Composition Root"]
App --> Feature["RatesFeature<br/>UI · MVVM-C"]
App --> Data["RatesData<br/>Repositories · DTO Mapping"]
Feature --> Presentation["PresentationCore"]
Feature --> Design["DesignSystem"]
Feature --> Domain["RatesDomain"]
Data --> Domain
Data --> Networking["NetworkingCore"]
Feature -. "compile-time" .-> Macros["AppMacros"]
Macros -.-> Plugin["AppMacrosPlugin"]
Ratewise is the composition root. It constructs the concrete data layer and injects domain abstractions into feature modules. Dashed arrows represent compile-time tooling.
| Module | Responsibility |
|---|---|
RatesDomain |
Currency models and repository contracts |
NetworkingCore |
Endpoints, HTTP loading, status validation, error mapping, and safe request logging |
RatesData |
ExchangeRate-API integration and versioned file cache |
RatesFeature |
Rate list and currency picker MVVM-C features |
PresentationCore |
Coordinator abstraction and reusable loadable state |
DesignSystem |
Spacing, typography, color, and animation tokens |
AppMacros |
Public compile-time macro declarations |
AppMacrosPlugin |
SwiftSyntax macro implementations |
RateListViewModelrequests rates throughRatesRepository.CachedRatesRepositoryattempts the remote request.- A successful response is returned and written to disk.
- Cache write failures do not invalidate a successful remote response.
- A remote failure falls back to the latest cache for the selected base currency.
- Cancellation remains cancellation and never becomes a cache fallback.
The cache uses:
- A versioned JSON payload
- Separate files for each base currency
- Actor-serialized file access
- Atomic writes
- Data-layer persistence DTOs
- Swift 6 language mode
- Swift 6.3.2 toolchain
- Strict concurrency
- UIKit with programmatic layout
- MVVM-C
- Tuist 4.118.1
- Swift Testing
- SnapshotTesting
- SwiftFormat
- SwiftLint
- GitHub Actions
- OSLog
- macOS with Xcode 26.5
- Homebrew
- iOS 26.0 or later
Tool versions are pinned in .mise.toml:
- Tuist 4.118.1
- SwiftFormat 0.61.1
- SwiftLint 0.65.0
Clone the repository:
git clone https://github.com/Okariuss/ForexApp.git
cd ForexAppInstall the pinned tools and project dependencies:
make setupGenerate the workspace:
make generateOpen the generated workspace:
open ForexApp.xcworkspace| Command | Description |
|---|---|
make generate |
Generate the Xcode workspace |
make format |
Format Swift source files |
make format-check |
Verify formatting without modifying files |
make lint |
Run SwiftLint |
make build SCHEME=ForexApp |
Build the application |
make test SCHEME=RatesFeature |
Test a specific scheme |
make test-all |
Test all application and module schemes |
make check |
Run formatting, lint, and all tests |
Tests run with a deterministic English/US locale by default. Override it when needed:
make test SCHEME=RatesFeature TEST_LANGUAGE=tr TEST_REGION=TRThe test suite covers:
- Domain validation
- Endpoint construction and networking behavior
- Transport, HTTP, decoding, and cancellation error mapping
- Remote/cache repository behavior
- Atomic file-cache persistence
- Rate-list loading, refresh, amount, and currency behavior
- Currency search and selection
- Coordinator routing and transition selection
- Localization key coverage and formatted strings
- VoiceOver labels, values, hints, and traits
- Component snapshots across appearance and content-size variants
GitHub Actions verifies every pull request and every push to main by running:
- Pinned tool installation
- Project dependency installation
- SwiftFormat validation
- SwiftLint
- All test schemes
- Application build
Ratewise supports:
- Dynamic Type
- Accessibility text categories
- VoiceOver
- Reduce Motion
- Semantic accessibility labels and values
- Selected-state announcements
- Layout adaptation for large content sizes
- Minimum interactive hit areas
The application currently supports:
- English
- Turkish
Localization resources are owned by RatesFeature and loaded from the framework bundle.
Rates are provided by ExchangeRate-API.
The configured endpoint is:
https://open.er-api.com/v6/latest/{baseCurrency}
The base URL and default currency are supplied through xcconfig-backed application configuration.
Xcode may occasionally report:
AppMacrosPlugin produced malformed response
If this occurs after otherwise valid macro builds, clean the affected scheme:
xcodebuild \
-workspace ForexApp.xcworkspace \
-scheme RatesFeature \
-configuration Debug \
cleanThis can be caused by stale DerivedData or plugin-process state. If cleaning the scheme does not resolve it, restart Xcode and rebuild the workspace.
Ratewise does not collect personal data, does not track users, and does not use analytics or advertising SDKs.
See Privacy Policy.
Ratewise is an independent portfolio project built by applying the architecture, testing, accessibility, and engineering practices I learned during the iCommunity Advanced iOS Bootcamp.
Special thanks to my instructor, Seyfeddin Başsaraç, for his guidance throughout the program.

