A trader wants to see the Bitcoin rate on the main screen without opening the app. But a cryptocurrency widget is not just a pretty picture: it must live by the rules of the mobile OS. On iOS that means WidgetKit + SwiftUI, on Android — Jetpack Glance or classic AppWidgetProvider. Both work on the snapshot principle: the system requests the current UI at certain moments, and what gets displayed is our responsibility. We rely on over 30 implemented solutions for crypto exchanges and 5+ years of mobile development experience. Budget optimization for your project starts with choosing the right widget architecture.
Why the Home Screen widget is trickier than it looks?
At first glance, a widget seems like a simple UI element. But its limitations in updating data, especially for cryptocurrencies, require a well-thought-out architecture. Let's break down the key problems and their solutions — based on our own experience.
How does WidgetKit limit updates?
WidgetKit does not allow the widget to make network requests in real time. The widget receives data via TimelineProvider, which returns an array of TimelineEntry with pre‑prepared data and timestamps. The system itself decides when to redraw the widget.
For a crypto widget, a typical strategy is to update every 15–30 minutes using TimelineReloadPolicy.atEnd or .after(date:):
struct CryptoPriceEntry: TimelineEntry {
let date: Date
let symbol: String
let price: Decimal
let change24h: Double
}
struct CryptoPriceProvider: TimelineProvider {
func getTimeline(in context: Context,
completion: @escaping (Timeline<CryptoPriceEntry>) -> Void) {
Task {
let price = try? await CryptoAPIClient.shared.fetchPrice(symbol: "BTC")
let entry = CryptoPriceEntry(date: .now,
symbol: "BTC",
price: price?.usd ?? 0,
change24h: price?.change24h ?? 0)
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: .now)!
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
completion(timeline)
}
}
}
More about the TimelineProvider mechanism
`TimelineProvider` is a protocol that defines three methods: `placeholder`, `getSnapshot`, and `getTimeline`. `getTimeline` returns an array of entries, each containing a date and data. The system uses these entries to render the widget at the corresponding points in time. After the last entry is displayed, the widget requests a new timeline. This cycle saves resources but limits the update frequency.
An important nuance: Apple adjusts the update budget. Widgets with high update frequency on low‑battery devices receive a reduced budget — updates start coming less frequently than requested. For trading apps requiring "data no older than 1 minute," a widget is not suitable — we honestly explain this to the client before development begins. We always analyze business requirements and offer alternatives, such as push notifications or Live Activity. Following the guidelines helps avoid App Store rejection and saves budget for rework.
Data transfer between the main app and the widget is done via App Groups + UserDefaults(suiteName:) or FileManager with a shared container. @AppStorage inside the widget works only with an App Group suite — without it, the widget won't see data written by the main app.
Sizes and UI adaptation
WidgetKit supports 4 sizes: .systemSmall, .systemMedium, .systemLarge, .systemExtraLarge (iPad only). For a crypto widget, we usually implement small (symbol + price + change) and medium (several coins in a row). SwiftUI in widgets does not support animations, ScrollView, or arbitrary tap areas — only Link for deep links.
How does Android solve the same tasks?
Jetpack Glance vs classic AppWidgetProvider
| Characteristic |
Jetpack Glance |
AppWidgetProvider |
| API |
Compose‑like |
RemoteViews |
| Date of appearance |
Relatively recent |
From the very beginning |
| Complexity |
Lower (declarative) |
Higher (imperative) |
| Limitations |
Not all Compose modifiers |
Full control |
Jetpack Glance is a Compose‑like API for widgets, appearing relatively recently. It is noticeably more convenient than classic RemoteViews, but has limitations: not all Compose modifiers are supported, and some APIs work differently than in regular Compose.
Data updates via GlanceAppWidgetManager.updateIf + WorkManager with a periodic task:
class CryptoPriceWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
val prefs = currentState<Preferences>()
val price = prefs[priceKey] ?: "—"
val change = prefs[changeKey] ?: "0.0"
provideContent {
Column(
modifier = GlanceModifier.fillMaxSize().background(Color.DarkGray).padding(12.dp)
) {
Text("BTC", style = TextStyle(color = ColorProvider(Color.White), fontSize = 12.sp))
Text(price, style = TextStyle(color = ColorProvider(Color.White), fontSize = 20.sp))
Text("$change%", style = TextStyle(
color = ColorProvider(if (change.startsWith("-")) Color.Red else Color.Green)
))
}
}
}
}
The minimum update interval via AppWidgetManager is 30 minutes (Android limitation). For more frequent updates, you need WorkManager with PeriodicWorkRequest, but on Android 12+ background tasks are regulated by Battery Optimizer — in Doze mode intervals stretch out.
Comparison of update mechanisms between iOS and Android
| Parameter |
iOS WidgetKit |
Android Jetpack Glance |
| Minimum interval |
15-30 minutes (system‑regulated) |
30 minutes (WorkManager can do more) |
| Update mechanism |
TimelineProvider |
GlanceAppWidget + WorkManager |
| Limitations |
Battery budget at OS level |
Doze mode, Battery Optimizer |
| Recommendation |
For widgets not requiring real‑time |
Similar |
How to set up WidgetKit for a crypto widget? (step‑by‑step)
- Add a Widget Extension target in Xcode, include WidgetKit.
- Create a
TimelineEntry structure with required fields (price, change, date).
- Implement
TimelineProvider: methods placeholder, getSnapshot, getTimeline.
- In
getTimeline, make an API request, form an entry, specify the next update date.
- Create a SwiftUI View for the widget using
Widget and StaticConfiguration.
- Configure App Groups to share data with the main app.
- Support multiple sizes via
supportedFamilies.
Typical mistakes when developing crypto widgets
- Ignoring update budgets on iOS — the widget stops updating at low battery.
- Missing fallback UI when the network is unavailable — the user sees an empty widget.
- Using the wrong suite for App Groups — data is not transferred.
- Too frequent updates on Android — conflict with Battery Optimizer.
What's included in the work
- iOS: WidgetKit extension,
TimelineProvider, SwiftUI layout, App Groups for shared data.
- Android: Jetpack Glance widget, WorkManager for updates.
- Integration with exchange rate APIs (CoinGecko, Binance, CoinMarketCap, or your own backend).
- Support for multiple widget sizes.
- Deep link from the widget to the desired app screen.
- Testing of behavior without network and with stale data.
- Guarantee of compatibility with App Store and Google Play (following guidelines).
Example workflow (our case)
For one project — a crypto wallet with a portfolio — we implemented an iOS widget. Client request: update every 5 minutes. We had to use a combination of WidgetKit + background task to maintain recency. On Android — Glance + WorkManager with a 15‑minute interval policy. Result: users returned to the app from the widget twice as often.
Timelines
3–5 days per platform. If both are needed, 5–8 days total, considering the common data fetching logic. The cost is calculated individually — contact us for a project evaluation within 1 business day. If you need a widget for your cryptocurrency app, get a consultation from our team.
Why is Native iOS Development the Best Choice for Complex Apps
The app crashes on cold start — EXC_BAD_ACCESS at the moment of initializing a singleton that accesses another singleton that hasn't been initialized yet. Or: a ViewController leaks memory because a closure captures self without [weak self], and that ViewController hangs in memory two transitions after the user left it. These are not hypothetical scenarios — they are the two most common classes of problems on iOS projects that come to us after another team.
We have been doing iOS development for over 5 years, delivered 40+ projects of varying complexity — from startups to enterprise solutions with millions of users. Each project undergoes 3 stages of Code Review, a custom set of UI tests (150+ test cases on average), and a mandatory run through Xcode Instruments before release.
Native iOS development with Swift means direct access to the platform. No middleware, no performance compromises, full control over what happens on every frame.
What Makes Native iOS Development on Swift the Choice for Enterprise Apps?
Native code guarantees compatibility with new Apple APIs on the day they are released, not after months of adaptation in cross-platform frameworks. For apps with latency-sensitive logic (financial terminals, medical monitors, AR navigation), this is critical. Swift with ARC and strict typing allows maintaining a crash-free rate of 99.9% with proper architecture.
SwiftUI or UIKit: What to Choose for Native iOS Development
By now, SwiftUI covers the vast majority of production tasks. But UIKit is not deprecated and will not disappear — Apple does not deprecate it but continues to add APIs. The real picture on large projects: a hybrid approach. SwiftUI for most screens, UIKit where SwiftUI hits limitations.
Which Scenarios Does SwiftUI Win Unconditionally
SwiftUI's declarative syntax reduces UI code by 3-5 times compared to UIKit. A settings screen with List, Toggle, Picker — that's 40 lines of SwiftUI versus 200 lines of UIKit with UITableViewDataSource delegates. Time savings on UI development reach 60%. Apple recommends starting new projects on SwiftUI (Human Interface Guidelines).
@State, @Binding, @ObservableObject (and with iOS 17, the @Observable macro) create a reactive link between data and UI without manual reloadData(). Changing a @State variable automatically redraws the affected part of the hierarchy. This works correctly if you understand how SwiftUI computes the diff — via Equatable and id in ForEach.
AsyncImage, NavigationStack with type-safe routing via NavigationPath, searchable, refreshable — these are ready-made patterns that UIKit requires implementing manually.
When UIKit Remains Necessary
UICollectionView with compositional layout and diffable data source — complex grids with different cell types, horizontal sections inside vertical scroll, dynamic cell sizes. SwiftUI LazyVGrid / LazyHGrid do not provide such control.
Custom transitions between screens. UIViewControllerAnimatedTransitioning and UIViewControllerInteractiveTransitioning — interactive pop gesture with partial progress, custom hero transition with precise frame control. SwiftUI matchedGeometryEffect covers some cases, but not all.
UITextView with TextKit 2. Rich text editor, custom attributes, custom rendering — TextKit 2 (available since iOS 16) switched to async layout, solving performance issues on long documents. SwiftUI TextEditor is a wrapper around UITextView without direct access to TextKit.
UIScrollView with custom behavior. scrollViewDidScroll, parallax effects, sticky headers with custom logic, pull-to-refresh with custom indicator. SwiftUI ScrollView with scrollPosition and onScrollGeometryChange (iOS 17) covers some cases, but not all.
How Do We Integrate SwiftUI and UIKit Step by Step
- Identify screens where SwiftUI gives maximum gain (lists, forms, settings) — usually 70-80% of screens.
- For performance-critical areas (complex collections, custom animations) leave UIKit.
- Use
UIHostingController to embed SwiftUI views into UIKit navigation stack.
- For backward compatibility, wrap UIKit components via
UIViewRepresentable.
- Coordinator pattern (UIKit) manages navigation at the flow level, screens are implemented in SwiftUI.
One pattern we use on projects: UIKit coordinator manages navigation, while the screens themselves are in SwiftUI. The coordinator creates a UIHostingController, passes ViewModel via initializer or @EnvironmentObject, and manages transitions. This gives clean separation: SwiftUI handles UI, Coordinator handles navigation.
How async/await and Combine Work Together
Before Swift 5.5, asynchronous code on iOS was built on Combine or callback chains. With the advent of async/await and Actor, concurrency has become part of the language. On new projects we use async/await as the primary tool for network calls and business logic, and Combine for reactive UI state binding.
// Correct — @MainActor guarantees UI updates on main thread
@MainActor
class UserViewModel: ObservableObject {
@Published var user: User?
@Published var isLoading = false
func loadUser(id: String) async {
isLoading = true
defer { isLoading = false }
do {
user = try await userService.fetch(id: id)
} catch {
// handle error
}
}
}
Combine remains indispensable for debouncing input, merging multiple Publishers (CombineLatest, Zip), and functional processing of value streams (map, flatMap, filter). In practice, 80% of projects use both approaches, choosing the tool for the task.
iOS App Architecture
MVVM — the basic pattern. ViewModel contains logic and @Published state, SwiftUI View subscribes via @ObservedObject or @StateObject. One rule: View knows nothing about URLSession, CoreData, UserDefaults.
Clean Architecture adds Repository and UseCase layers. UserRepository abstracts the data source (network vs cache). FetchUserUseCase contains business logic. UserViewModel calls UseCase and manages UI state.
TCA (The Composable Architecture) — a stricter pattern from Point-Free. State, Action, Reducer, Effect — everything explicit, testable, composable via Scope. Works well in large teams (5+ iOS developers) where predictability is important.
What's Included in iOS App Development
| Stage |
Deliverables |
| Analysis and Design |
Technical specification, architectural diagram, technology stack selection |
| Development |
Code compliant with App Store Review Guidelines, backend integration (REST/GraphQL) |
| Testing |
Unit tests (XCTest, coverage >75%), UI tests (XCUITest, 150+ scenarios), load testing via Firebase Test Lab |
| Publication |
Developer account setup, code signing, submission to App Store Connect |
| Support |
30-day warranty after release, updates for new iOS versions |
Tools Without Which No Release Is Complete
Xcode Instruments. Time Profiler shows where CPU spends time. Allocations — memory leaks and excessive allocations. Leaks — objects that are not freed. Before every release — a mandatory run.
Firebase Crashlytics. Crash-free rate, grouping by stack trace, breadcrumbs of events leading to crash. Set up in 30 minutes, provides visibility across the entire device fleet. On our projects, average crash-free rate is 99.8%.
Fastlane match. Manage certificates and provisioning profiles via an encrypted git repository. Eliminates the 'it builds locally but not on CI' issue once and for all. Saves up to 4 hours per build when signing manually.
XCTest + XCUITest. Unit tests for ViewModel and UseCase, UI tests for critical flows (onboarding, payment, authorization). On average, code coverage is 75%.
Typical iOS Project Mistakes and Their Solutions
| Problem |
Solution |
Memory leak due to self capture in closure |
Use [weak self] in all handlers where self does not need to outlive the closure |
| Provisioning Profile conflicts |
Set up Fastlane match and store certificates in a separate repository |
| Slow app start due to synchronous singleton initialization |
Move initialization to first call or use lazy var |
| App Store rejection due to Section 4.2 (minimal functionality) |
Conduct a preliminary audit using the App Store Review Guidelines checklist |
Process and Timelines
| Complexity |
Estimated Timeline |
| MVP (5–8 screens, basic API) |
6–10 weeks |
| Medium app (15–25 screens) |
3–5 months |
| Complex (payments, AR, CoreML, custom UI) |
5–9 months |
Cost is calculated individually after analyzing the technical specification and design. Typically, the first 2 weeks are spent on design, after which we finalize the timeline and budget.
Order turnkey development — we will evaluate your project in 2 business days and propose the optimal architecture. Contact us to discuss your task: we guarantee code quality, compliance with App Store Review Guidelines, and experience with projects of any scale. Get a consultation — we will help you choose the right stack and avoid common mistakes at the start.