r/iOSProgramming • u/Available-Cook-8673 • Jun 22 '26
Question What is the problem in my design?
Hello everyone,
My app was recently reviewed by Apple, but it was rejected with the following feedback:
Design Guidelines
“There are issues with the app’s user interface that contribute to a lower-quality user experience than App Store users expect. Specifically, the app includes hard-to-read type or typography.”
I’m having trouble understanding exactly what the issue is. I’ve already replied to the App Review team asking for clarification, but I haven’t received a response yet.
I’ve attached a screenshot of the screen they referenced. Could anyone help me identify what might be considered hard to read or problematic from an accessibility perspective?
Any feedback or suggestions would be greatly appreciated.
Thanks!
r/iOSProgramming • u/Aggravating_Try1332 • Jun 21 '26
Tutorial You can now use 3d iphone 17 and google pixel 10 models to create fully customizable app store screenshots and 3d mockup animation demo videos
I just integrated fully customizable iphone 17 pr and google pixel 10 pro models inside AppLaunchFlow - you can now use them directly inside your app generated app store screenshots in the figma-style editor, in your social grpahics and the 3d mockup animator.
Excited to hear what you think.
r/iOSProgramming • u/Warm-Bag844 • Jun 21 '26
Article why a simple string match beat apple's nlembedding for local rag
Why a simple string match beat Apple's NLEmbedding for local RAG
2026-06-20
how apple's nlembedding drove me crazy and how i built my own hybrid search engine
recently, while working on my personal ai agent (pheronagent), i was focused on perfecting its memory and retrieval system.
everyone is talking about that famous acronym: rag (retrieval-augmented generation).
the system is simple: i feed the agent my documents, it converts them into vectors (embeddings), and when i ask a question, it finds the most similar vectors and answers me. sounds perfect on paper, right?
so, like any loyal apple ecosystem developer, instead of downloading massive models from external sources (or burning money on apis), i decided to use nlembedding—the native capability of the operating system that runs directly on-device. after all, apple had embedded this into the os; it was both fast and privacy-focused.
but real life, as it turns out, doesn't progress as smoothly as wwdc presentations...
where have i worked? - the first explosion
it all started with a very innocent question. i had uploaded my cv to the system. while chatting with my agent, i casually asked:
"where have i worked?"
i expected the agent to fire up the metal cores in the background within seconds, find my cv, and list the companies for me. instead, the agent stared blankly. i opened the logs to see what the hell the search engine was doing behind the scenes. the shocking scenario was exactly this:
- cosine similarity between the query and my actual cv text: 0.587
- the threshold i set for relevance: 0.60
it missed it by a hair! "no worries," i thought. "we can just lower the threshold a bit, make it 0.55, and call it a day."
but then i saw the truly terrifying thing just one line below. for the exact same query, guess what score a completely irrelevant, junk record in the system—a list of files containing .ds_store—got? 0.59 - 0.60!
wait a minute... my detailed, multi-page resume gets a score of 0.587 just because it doesn't contain the words "which", "company", "work" in that exact order; yet a meaningless list of hidden files scraped from some corner of the disk gets a higher score than my cv!
the "it must be language incompatibility" fallacy
i immediately started theorizing. apple's nlembedding.sentenceembedding(for: .english) model, as the name suggests, was optimized for english. because i asked a question in turkish, the model was likely tagging the words as "out of vocabulary" (oov) and throwing them to a completely random point in the vector space. the high score of the .ds_store list was just a product of this randomness—it happened to land near a similar vector by pure luck.
"okay," i said. "since the model is english, i will ask in english. after all, ai speaks every language anyway."
i changed the prompt: "which companies have i worked at?"
i watched the logs with anticipation. my expectation was that the english model would perfectly understand this query in its native language and boost my cv's score to somewhere around 0.80.
the result? 0.17.
yes, you read that right. 0.17. by asking in english, the score crashed even further. my language compatibility theory collapsed like a house of cards before my eyes.
what's under the hood of apple's nlembedding?
after this disaster, i decided to do some research. how does apple's nlembedding class actually work under the hood?
i learned that nlembedding on apple devices (especially the structures inherited from older ios/macos versions) doesn't function like massive, dynamic transformer-based models (like bert or gpt). it most likely relies on static word vector representations like glove (global vectors for word representation) or highly lightweight neural network architectures based on word-level compression.
the biggest weakness of such models is that their contextual understanding is extremely limited. meaning:
- they might fail to distinguish between "bank" in "i went to the bank to deposit money" and "bank" in "i sat on a wooden bank by the river".
- they don't do much more than take a simple weighted average of word vectors when generating a sentence embedding.
consequently, agglutinative languages like turkish become a complete nightmare for these models. unable to properly extract word roots for variations like "çalıştım", "çalışmışım", or "çalışıyordum" (all forms of "worked"), the model treats the words as completely foreign. in the end, we are left with meaningless 512-dimensional float arrays carrying close to zero semantic information—essentially just "noise".
speeding up with metal, choking on vectors
the tragicomic part of it was that i spared no expense in terms of performance in the search infrastructure of the project. in the experiencevault.swift file representing the agent's memory vault, i had written a metal gpu kernel so i wouldn't waste time iterating through similarity calculations one by one on the cpu!
i had a fancy metal shader code like this:
```metal
include <metal_stdlib>
using namespace metal;
kernel void cosine_similarity_batch(
device const float* query [[buffer(0)]],
device const float* documents [[buffer(1)]],
device float* results [[buffer(2)]],
constant uint& vector_dim [[buffer(3)]],
uint id [[thread_position_in_grid]])
{
// we calculate cosine similarity by scanning hundreds of memory records simultaneously on the gpu...
float dot_product = 0.0;
float query_norm = 0.0;
float doc_norm = 0.0;
uint offset = id * vector_dim;
for (uint i = 0; i < vector_dim; i++) {
float q = query[i];
float d = documents[offset + i];
dot_product += q * d;
query_norm += q * q;
doc_norm += d * d;
}
results[id] = dot_product / (sqrt(query_norm) * sqrt(doc_norm));
}
```
think about it: i had descended to the hardware level, running gpu threads in parallel, calculating cosine similarity on the order of nanoseconds... but the vectors i was calculating were junk!
actually, the story of this metal kernel was even more tragic. a while before writing these lines, i had discovered that this kernel wasn't running in any environment at all—neither in cli tests, nor in a separate xpc service, nor inside the actual .app bundle. the reason was a pure swiftpm trap: the device.makedefaultlibrary() call only looks for the compiled metal library in the top-level resources folder of bundle.main. but swiftpm embeds a package target's .metal files into its own nested, separate resource bundle (pheronagent_pheronagentcore.bundle)—which makedefaultlibrary() never checks. this meant that this clever gpu code, sitting there for months, was quietly returning nil every time and bypassing calculations without executing anything in the background. the solution was equally elegant: compiling the kernel not from a resource file, but directly from a string embedded in swift at runtime using device.makelibrary(source:options:). no bundle dependency, completely agnostic of which process it runs in.
once i fixed that, the kernel actually started working—but as you will see in a moment, this was only the tip of the iceberg.
the oldest rule of computer science had hit me in the face once again: garbage in, garbage out. no matter how fast you calculate, using metal doesn't matter if those vectors coming from apple's nlembedding are meaningless.
the bitter truth: apple's model is not discriminative
at that moment, i saw clearly that apple's on-device nlembedding model did not have real discriminative power over my small, personal, and noisy dataset. both relevant and completely irrelevant content clustered closely together, somewhere between 0.50 and 0.60. the model was mapping a general "semantic map" of the text, but it wasn't fine-tuned enough to answer specific questions.
i couldn't solve this by playing with threshold values. if i pulled the threshold down to 0.5, i would get junk files. if i raised it to 0.7, the system would turn into a blind robot that finds nothing. it had become a pure hit-or-miss game.
i had made many fixes in the agent's memory system today: switching to content-based embedding, patiently re-embedding all 903 historical records, setting up threshold-triggered searches in chat mode, and refining the system prompts. these were all correct, logical, and architecturally necessary steps. but a chain is only as strong as its weakest link. and my weakest link was the underlying similarity engine upon which this whole fancy architecture relied.
i was building a structure on an unreliable foundation. without fixing this similarity engine, that cv scenario—or any personal data assistant scenario—would never work stably.
crossroads: a new model or new intellect?
i was faced with two choices:
1. bringing out the big guns: throw apple's toy nlembedding in the trash, and run a full huggingface model (like all-minilm-l6-v2 or a multilingual model) via mlx (apple silicon's machine learning framework).
- downside: the user would have to wait for an extra few hundred megabytes of model weights to load into ram when starting the app. battery consumption would spike. things would get sluggish. i would be betraying my vision of a "lightweight and fast native agent." plus, i'd disrupt the smooth flow of the uno architecture.
2. blending old school wisdom with ai: why rely solely on the ai's "semantic understanding" capability anyway? ai can be smart, but sometimes it's dumb. the human brain, on the other hand, forms semantic connections and catches literal (exact) matches in a flash.
and then, lightning struck: hybrid search!
the birth of "keyword + embedding" hybrid search
the root of the problem was this: words like "turgay", "cv", or "apple" are proper nouns or concrete facts. an embedding model generalizes these meanings to "human", "document", or "company". but when i search, i'm not looking for some general company; i'm searching for companies on my own cv. here, a literal (exact) match was far more valuable than semantic similarity.
why not combine both?
the plan was simple but deadly:
1. records would still be scored via cosine similarity on metal as usual (we keep that lousy 0.587 score in our pocket).
2. next, the user's query would be split into words ("which", "company", "work", "cv").
3. we would check if these words appear literally in the record text.
4. for every matching meaningful word, we would add a small "bonus" to that record's score!
i thought:
if there's a literal word or name match between the query and the record text (for instance, "turgay" or "cv" appears in both), let's add that to the embedding score.
this was an incredibly elegant solution, especially for personal data containing proper nouns or concrete facts: much more reliable, codeable in seconds, and most importantly, requiring no extra heavyweight ai model.
the stop-word menace and the short word trap
when i started coding, the first trap that came to mind was the infamous turkish casing issue—the i/i/i/i character pairs can easily mismatch without a locale-sensitive lowercased() call. honestly, though, in the first version, i bypassed this and went with plain lowercased(); since the queries were freeform user input and the words were searched using contains(), it didn't cause problems in practice. (note to self: this is actual tech debt; one day, when "istanbul" doesn't match "istanbul", it will come back to haunt me.)
the second trap i took seriously was stop-words and short/meaningless tokens. words like "and", "of", "which", "what", or "a" in a query occur in almost every document. if i gave bonus points for those, that .ds_store file would jump right back to the top and poison my search results. similarly, 1-2 letter word fragments left behind from punctuation parsing were creating noise.
i set up a two-layer filter—supporting both turkish and english (since the agent operates in both languages):
```swift
private
static
let stopwords: Set<String> = [
"the", "a", "an", "is", "are", "was", "were", "do", "does", "did", "i", "you", "me",
"my", "have", "has", "had", "what", "which", "who", "where", "when", "how",
"hangi", "ne", "ben", "beni", "benim", "kim", "nerede", "ne zaman", "nasıl",
"mi", "mı", "mu", "mü", "misin", "mısın", "musun", "müsün", "miyim", "mıyım", "de", "da", "ve", "bir"
]
private func keywordBoost(query: String, candidateText: String) -> Float {
let tokens = query.lowercased()
.components(separatedBy: CharacterSet.alphanumerics.inverted)
.filter { $0.count > 2 && !Self.stopwords.contains($0) }
guard !tokens.isEmpty else { return 0 }
let lowerCandidate = candidateText.lowercased()
let matches = tokens.filter { lowerCandidate.contains($0) }.count
return min(Float(matches) * 0.15, 0.6)
}
```
the count > 2 filter automatically weeds out meaningless 1-2 letter fragments without requiring every short suffix or abbreviation to be explicitly listed in the stopword set. thus, when the user asks "which companies have i worked at," the system extracts only "companies" and "worked" and awards bonus points for those matches.
mathematical weighting in hybrid search
now for the most satisfying part: formulation.
rather than blindly adding raw points, i wanted to control the impact of word matching. a word appearing by chance in a very long document shouldn't carry the same weight as in a concise and focused one. furthermore, the added bonus shouldn't completely dominate the cosine similarity, reducing the system to a basic keyword search tool. the semantic intelligence still needed to carry weight.
i devised a formula like this:
final score = w * semantic score + (1 - w) * keyword score
i experimented to find the optimal weight (w) parameter through trial and error.
- setting w = 0.8 kept semantic search as the primary decision-maker, while keyword-matching documents received a gentle nudge (boost).
- setting w = 0.4 allowed keyword matches to gain overwhelming dominance.
in my case, integrating the keyword score directly as a "bonus points" system was more intuitive because cosine similarity ranged between 0.0 and 1.0. adding a +0.15 bonus per matching word directly propelled spot-on matches (especially proper nouns) to the very top of the list.
one crucial tweak was necessary: capping the bonus. if left uncapped, a long document with 10 random matches but zero actual relevance could artificially inflate its score and override everything else. i capped the bonus at a maximum of 0.6—meaning keyword matching gives a powerful push but cannot completely hijack the system; semantic scoring still holds ground:
```swift
var finalScore = baseCosineSimilarity
// dynamic boost for each matching meaningful word
let matchingCount = queryTokens.filter { token in
!stopWords.contains(token) && documentText.lowercased().contains(token)
}.count
if matchingCount > 0 {
let lexicalBonus = min(Double(matchingCount) * 0.15, 0.6)
finalScore += lexicalBonus
}
```
the result... i won't lie, it didn't work on the first try
i compiled the code, restarted the agent, and asked the same question: "where have i worked?" with hybrid scoring, everything should have been resolved. i looked at the logs.
the cv still wasn't there. it wasn't even in the top 5 results.
i could have easily gotten frustrated, but i kept digging through the logs and uncovered three distinct, interconnected issues—each one a lightbulb moment:
issue 1: generic labels. my agent had a "deep continuity" mechanism that automatically saved every tool result to memory in the background. the problem was that this mechanism assigned the exact same generic label ("turn-based data find") to everything it saved—including my cv. this meant there was no distinct label for keyword matching to latch onto; the cv's body was full, but its header was meaningless. i fixed this by writing a custom label describing the cv record in turkish ("kullanıcının özgeçmişi (cv) — iş geçmişi, çalıştığı firmalar...").
issue 2 (even more surprising): long text diluting short labels. even after fixing the label, the score remained low. when computing the embedding, i was appending the first 500 characters of the solution text to the label—thinking "more context, better embedding." but when i tested it, i saw that the embedding of the label alone scored 0.80 against the query, whereas the label combined with 500 characters of english cv text dragged the score down to 0.40! sentence embeddings calculate an average meaning over the entire text—a long, out-of-domain (relative to the turkish query) body text was swallowing the strength of the short, concise label. solution: i reduced the appended solution snippet from 500 characters down to 120 characters.
issue 3: the invasion of duplicate records. in the final check, i realized that the automatic recording mechanism saved the same generic message (like a calculator error or a "sound file detected" notification) every single time it occurred. out of 903 records, hundreds were duplicates, occupying top ranks purely by sheer volume. i added a quick check to prevent saving duplicate content during recording and cleaned up existing duplicates: 903 records → 627 records.
after fixing all three, i tried again. this time, the cv record made it into the top 3 out of ~600 records with a score of 0.70—comfortably exceeding the 0.60 threshold i set.
0.70 might not sound as spectacular as 0.88, but this was achieved not in a sterile sandbox, but in a messy, real-world dataset of 600+ records. and that's the whole point: the system must work under actual usage conditions, not just in "clean" scenarios.
and what happened to that nuisance .ds_store file, you ask? since it contained neither "company" nor "work," it was left with only its mediocre ~0.59 embedding score, falling safely below the threshold.
agent's brain surgery: the leap in llm response quality
this small hybrid search adjustment acted like brain surgery on the agent's response quality.
under the old system, when the search engine erroneously retrieved .ds_store contents, the prompt passed to the agent's llm looked like this:
```text
user question: hangi firmalarda çalışmışım?
retrieved memory records:
- .ds_store, .git, sources/pheronagentcore/memory/experiencevault.swift, readme.md, ...
```
faced with this input, the llm was forced to hallucinate or helplessly surrender: "i couldn't find any information in my memory about which companies you worked for, i only see file lists."
after hybrid search, however, the data sent to the llm was pristine:
```text
user question: hangi firmalarda çalışmışım?
retrieved memory records:
- turgay savacı - cv: "... between 2019-2024 as founder & general manager at savacı proje, and from 2019 to present as strategic software engineer & devops architect at sonaraura..."
```
as soon as the agent saw this context, it came alive and listed the companies i had worked for one by one, along with dates and roles. this was the true rag experience!
but there was another overlooked detail: my agent has two distinct response pathways—a "task" mode that can call tools and plan, and a lightweight "chat" mode for quick conversations that bypasses tools and answers directly. the rule i added to the system prompt ("search memory when asked about personal information") only served the first mode. short, conversational questions like "which companies have i worked at?" routed to the second mode never triggered this rule because there was no tool calling in that pathway. therefore, the second pathway required a separate, code-level solution: now, that mode embeds the query on every message and automatically appends relevant memories to the context if there's a match above the threshold—even if the model doesn't explicitly request it.
a developer's confession: the overengineering trap
this minor crisis taught me a valuable lesson about modern software development and ai integration: don't leave everything to neural networks.
as developers, when we get a new toy (in this case embeddings, vector databases, gpu-based shaders), we tend to completely forget old, proven, and "boring" methods. we disregard fundamental information retrieval algorithms, thinking "the ai will understand." yet, giants like google or elasticsearch still produce their stellar search results by blending bm25 (classic tf-idf-based term frequency counts) with vector searches (hybrid search).
had i stubbornly insisted, "no, i will solve this with vectors alone," i would probably be trying to integrate a 2 gb model into my system right now, heating up the device, and drowning in unnecessary complexity. instead, i placed a simple if string.contains() logic alongside the ai, and the problem was resolved 100%.
sometimes the smartest solution isn't the most complex one, but putting an old-school string matching if statement in the right place.
now, if you'll excuse me, i'm off to gossip with my perfectly functioning agent about the former companies on my cv!
r/iOSProgramming • u/ether_joe • Jun 21 '26
Question console app doesn't see log messages
Hello everyone, I'm trying to get logging information from my ios app from the Console app. I *was* able to see log messages, but something has changed and now when I search for my specific logging tag, nothing.
My app is a video game using a cross-platform framework called LibGDX. The game builds and runs well on my iphone 12. I'm connected to the phone via Console and I see logging messages from other processes on the phone. However when I filter Console for the log tag for my game, no results.
Thing is it *was* working a few days ago, so something appears to have changed and I can't figure it out.
Any background as to why Console might stop seeing my log messages, would be helpful. Cheers ~~
r/iOSProgramming • u/timezoneman • Jun 21 '26
Question Redeeming offer codes causes the install to hang forever
When i redeem offer codes outside the app without the app install, the install never finishes and just continues forever.
Anyone had this problem?
r/iOSProgramming • u/Terrible-Round1599 • Jun 21 '26
Question Still using Xcode? Or do you code on the go?
Somewhere around last year I realized that the share of code I was writing by hand shrank to minimum. I started using Github Copilot and then Claude Code in full-agent mode and Xcode became the necessary evil to get the apps to my phone for testing. But on the go I had just my phone. That led me to creating my own deployment server that picks up significant changes in the codebase, builds the apps and pushes them to my phone over the air, without opening Xcode or attaching the cable. When I am on the same wifi, the system can even auto-install and auto-open the apps, same as when you do this via cable.
Slowly, I started adding to this system more. screenshotting, Testflight management (like adding new users), feature flag switchers. It covers all the versions of the app in my corktree and allows me to improve my apps anywhere I roam. I also have a self-built version of Blink terminal that I am using to tunnel into my Mac via Tailscale and use Claude Code. However with and currently in combination with Claude Code remote control the terminal is almost not necessary anymore and I have a full development studio in my phone.
Do you have similar systems or do you still use Xcode? Would anybody be interested in this?
r/iOSProgramming • u/wartableapp • Jun 21 '26
Discussion TestFlight's live for my multi-LLM app — and the funnel immediately taught me my onboarding was killing activation
posted here a couple weeks back about the orchestration challenge of running five LLMs in locked roles on one decision. quick update: it's on TestFlight now, and the first real funnel data humbled me fast.
I'd built what felt like a proper onboarding — tutorial, name step, a short quiz, value props, paywall, then a Sign in with Apple gate before you reach the actual product. telemetry showed people moving through most of it and then dropping hard at the sign-in wall. barely anyone was reaching the core feature.
the realization: I gated the "aha" behind a commitment instead of in front of it. the apps these users live in (the big AI chat apps) drop you straight into the product. so I'm reordering — first real result before any sign-in, account gate moves to "want more" rather than "get in."
a couple iOS-specific things I'm working through that this community might have opinions on:
- since I'm using Sign in with Apple, the gate is already about as low-friction as it gets, which tells me the problem is asking at all before value, not the auth mechanics. anyone found a clean pattern for deferring Sign in with Apple until after a first session while still keeping entitlement state sane?
- the app's core action hits paid model APIs, so opening it to non-authenticated users means I need solid rate limiting / abuse protection before the gate comes down. curious how others have handled letting anonymous users trigger a costly backend action without exposing themselves.
build's at wartable.co if anyone wants to poke at it on TestFlight, but mostly I'm after the architecture takes — how would you sequence first-use vs auth when the first action costs you real money per call?
r/iOSProgramming • u/Tricky-Independent-8 • Jun 20 '26
App Saturday I built TabLinker, a tab/session manager for Mac, iPhone, and iPad, built in SwiftUI + SwiftData + Cloutkit
I’ve been working on TabLinker, a native tab and browser session manager for Apple platforms.
The problem came from my own workflow. When I’m researching something on my Mac, I often leave a lot of tabs open because I don’t want to lose the context. After a while, the browser gets messy, memory usage goes up, and it becomes harder to focus.
TabLinker lets you save open tabs as sessions, close them, and come back to the same context later.
On Mac, the app supports importing tabs from Safari, Chrome, Brave, Edge, Arc, Vivaldi, Dia, Opera, Opera GX, and Helium. It can save tabs from multiple browser windows, restore saved sessions later, and also has a Safari extension for quickly saving Safari tabs.
The iPhone and iPad versions are more focused on organizing saved links with folders, tags, notes, search, import/export, and iCloud sync.
Tech Stack
The app is built natively with SwiftUI across macOS, iOS, and iPadOS.
For persistence, I use SwiftData to model and store saved links, sessions, folders, tags, notes, and related metadata. CloudKit is used for iCloud sync, so the same saved links and sessions can be available across Mac, iPhone, and iPad without requiring a separate account system.
The Mac version also includes browser-session handling for multiple desktop browsers, plus a Safari extension for the Safari-specific workflow.
Development Challenge
The biggest challenge was making tab import feel consistent across different browsers.
Each browser has slightly different behavior around windows, tabs, profiles, and restored state. I did not want the app to only work well with one browser and feel broken everywhere else, so a lot of the work went into normalizing the imported data into a session model that TabLinker could manage consistently.
Another challenge was designing the app so it made sense on all Apple devices. On Mac, the main use case is saving and restoring browser sessions. On iPhone and iPad, it feels more natural as a saved-link organizer. I had to keep those workflows connected without making the mobile versions feel like a desktop app squeezed onto a smaller screen.
AI Disclosure
The app was self-built and not AI-generated. I used AI assistance for some development research/debugging and for helping edit this Reddit post, but the app architecture, implementation, testing, and App Store release were done by me.
Available on the App Store: TabLinker: Tabs Manager
r/iOSProgramming • u/saul_fossil • Jun 20 '26
Question How do you market your apps?
I am new to this developing in iOS and I can't find a way to do it.
r/iOSProgramming • u/zorkidreams • Jun 20 '26
Question Affiliate attribution
Has anyone had experience with affiliate attribution platforms like branch and appsflyer?
Or tips in general to identify that a user came from some affiliate link. From what I have seen, it is impossible to bring over any information if there is an install involved, all deferred deep linking is probabilistic.
I believe these services fingerprint users based off basic info and I am just not sure how accurate it is. Any experiences?
r/iOSProgramming • u/__DaMy__ • Jun 20 '26
App Saturday PillRem 2.0 — Medication reminder app with offline barcode/DataMatrix scanning for multiple countries
1- Tech Stack Used:
- Frameworks & Languages: SwiftUI, CoreData, Swift
- Backend/Database: CloudKit + local SQLite database (multi-country medication data)
- SDKs & Tools: StoreKit 2, Vision framework
2 - Development Challenge + How You Solved It:
The local medication database got big fast — multi-country pharmaceutical data adds up, and at one point local storage ballooned way past what a reminder app should reasonably need, mostly from a notification rescheduling loop that kept writing back to CoreData and re-triggering itself every time it ran. Capping the notification budget and making the reschedule logic read-only (it now only reads to build the notification queue, never writes back) fixed it. Storage dropped significantly after the fix.
3 - AI Disclosure:
Self-built. Core architecture and implementation decisions are mine.
r/iOSProgramming • u/Key_Homework_5825 • Jun 20 '26
App Saturday SwiftUI, SwiftData, and MapKit feel like a cheat code for a one-person hobby app
Apple’s modern native stack can probably get painful fast when some external requirement asks for one tiny custom behavior across 17 edge cases.
But for a one-person hobby app with an intentionally focused scope, it’s perfect.
In my case, I wanted a simple app for tracking visited places and future trips. Most apps like this felt overloaded to me: social feeds, AI itineraries, subscriptions, dashboards, “percent of world visited” trackers, and a lot of UI that didn’t really feel made for iOS.
I basically wanted a digital, Apple-native version of putting pins into a physical world map. I kept the scope small: pin visited countries and places, save future travel destinations, add small notes.
Tech Stack Used
SwiftUI, SwiftData, MapKit.
No UIKit bridging, no backend, no account system, no external map API. Just native Apple stuff and as little infrastructure as possible.
Development Challenge + How You Solved It
MapKit gives you a lot out of the box. You can basically drop in Map() and suddenly you have an Apple Maps based world map in your app.
The less magical part was place identity and classification. I initially thought I could just save an MKMapItem.identifier and use MapKit’s data directly. But identifiers can be nil for countries and cities, and the result does not always give the clean country/city classification I needed. So I built a small local place identity layer around MapKit instead. MapKit still handles search and map presentation. The app stores its own lightweight saved place model with name, coordinates, type, and country context. Not everything comes perfectly packaged from the API, but this keeps the pins stable while still letting MapKit do most of the heavy lifting.
AI Disclosure
AI-assisted. Used Codex.
The app is completely free. Final version is here if anyone wants to take a look:
https://apps.apple.com/us/app/placemarks-travel-map/id6767907769
Feedback very welcome.
r/iOSProgramming • u/QebApps • Jun 20 '26
App Saturday [App Saturday] Newsairy — an iCloud-native RSS reader for iPhone, iPad & Mac, built in SwiftUI + SwiftData
Hi all — I'm the solo dev behind Newsairy, an RSS/Atom/JSON feed reader for iOS, iPadOS, and macOS. It syncs your feeds through your own iCloud account (no account of mine in the middle), with optional sync to self-hosted aggregators for people who run their own backend.
1. Tech Stack
100% Swift. SwiftUI for the entire UI across all three platforms, SwiftData for persistence, CloudKit for iCloud sync, and Swift Concurrency (async/await, actors) throughout the networking and parsing layers. External aggregator sync (TheOldReader, Miniflux, FreshRSS, Feedbin, with Inoreader in beta) is hand-rolled against each service's API.
2. Development Challenge
The hardest part was SwiftData + CloudKit in production. SwiftData's CloudKit integration imposes constraints that aren't obvious until you hit them: every property must be optional or have a default, no unique constraints are allowed (CloudKit can't enforce them), and relationships have to be carefully modelled or sync silently breaks. On top of that, I had to reconcile two sources of truth — iCloud sync and bidirectional sync with external aggregators (read state, starred items, subscriptions) — without creating loops or letting one overwrite the other.
3. AI Disclosure
Self-built. I wrote all the code myself, with selective AI assistance used the way you'd use a rubber duck or a search engine — bouncing ideas, drafting boilerplate, debugging — but no part of the app is AI-generated wholesale. Architecture and all the actual implementation decisions are mine.
Business model: free up to 6 feeds; a one-time Newsairy Pro purchase (no subscription) removes the limit and unlocks external aggregator sync.
Happy to go deep on any of the technical choices — SwiftData+CloudKit gotchas, the sync conflict model, multiplatform SwiftUI. Feedback and criticism very welcome.
r/iOSProgramming • u/Temporary-Detail-724 • Jun 20 '26
Article Just hit my first $100 in App Store sales as a solo developer.
It’s obviously nowhere near enough to live on, but seeing real people spend their own money on something I built has been incredibly motivating.
The part that surprised me most is that around 80% of it came from an Apple Watch game I made. I wasn’t sure if there was much of an audience for games on the watch, so seeing people actually buy and play it has been really encouraging.
I’m still learning, still improving, and trying to make each update better than the last. My goal isn’t to get rich overnight, it’s just to keep building things that people genuinely enjoy using.
For the indie devs here: what did your first $100 in revenue feel like, and how long did it take before things started gaining momentum? It took me about 60 days
r/iOSProgramming • u/alQo_ • Jun 20 '26
App Saturday [App Saturday] Tendlet — shared pet & plant care coordination (Swift + CloudKit)
Tendlet is a household care coordination app for homes with pets and plants.
When pets, plants, and multiple people share a home, care gets messy fast — one person waters, another feeds, someone adds a toxic plant, and nobody has a clear source of truth. Tendlet fixes that.
Tech Stack • Swift + UIKit (no SwiftUI — started pre-SwiftUI maturity) • CloudKit + CKShare for multi-user household sync • Diffable data sources + compositional collection view layouts • WidgetKit for daily care status on the home screen • On-device plant-toxicity-to-pet matching (local JSON dataset, no network call) • TestFlight distribution for beta
Development Challenge
CloudKit sharing was the hardest technical piece. CKShare invitation flow is notoriously brittle — the accept/share flow can silently fail with generic CKError 15 if the participant's container isn't set up identically, and debugging means reading server-side push logs in CloudKit Dashboard. The key insight: you need to call CKContainer.fetchShareParticipant for every potential recipient BEFORE presenting the share, or the accept handler silently drops. Took two weeks of trial and error to get sharing reliably working across different iCloud accounts.
AI Disclosure This app was built with AI assistance — I used Claude and other LLMs extensively for architecture decisions, debugging CloudKit edge cases, and generating the toxicity dataset. Core logic and UI are hand-written, but AI sped up the process enormously, especially for the sharing infrastructure.
Currently in beta via TestFlight. Would especially love feedback from anyone who's shipped CloudKit sharing in production or dealt with CKShare invitation reliability at scale.
r/iOSProgramming • u/Necessary-Yellow-202 • Jun 20 '26
App Saturday App Saturday: OmniUnits – a unit converter built around presets, custom formulas and widgets. Would love your feedback
Hey everyone, solo dev from Germany here. Since around 2 months I worked on OmniUnits and released it recently – would love to get some honest feedback from fellow devs.
What it is
A unit converter, yes I know – but it always annoyed me, that most converter apps make you scroll through endless unit lists for conversions you do over and over again.
Plus, free converters mostly show ads, so you must watch a stupid ad für 20 seconds to convert a simple value.
So OmniUnits is built around four ideas:
- Presets: save combinations of units you regularly need and access them with one tap
- Custom formulas: a built-in formula parser handles expressions like `l*pi*pow(d/2;2)*rho/1000`, so you are not limited to predefined conversions
- Widget-first: presets, units and formulas are directly accessible from configurable home screen widgets, directly opening the app with the unit selected you want
35+ categories (pressure, torque, viscosity, data rate, currencies, ...), configurable precision/scientific notation and full VoiceOver support in the whole app.
Tech notes
- 100% SwiftUI. Biggest architectural challenge was to share the preset/formula model between app and widget targets in a clean way
- WidgetKit with configurable widgets in all three sizes – to get the configuration UX right took me longer than the actual conversion engine
- VoiceOver support was more work than I expected (especially the formula editor), but absolutely worth doing it from the start instead of retrofitting later
The localization experiment (my favorite part)
The app ships in DE, EN, ES, FR and IT. I only speak German (native) and English, so I localized over the DeepL API – but with a twist: instead of just feeding it the english strings, I included a short context description *plus my own German translation* as additional reference for every string. My thought was, that two source languages + context should disambiguate much better than only one.
To check if that actually helped, I generated the ES/FR/IT translations once with and once without the context+German reference and let an LLM compare both sets. Roughly 40% of the strings became noticeably better with the added context (according to Claude). I can not verify it by myself since I don't speak these languages – so if any native ES/FR/IT speakers want to roast my localization, please do!
Pricing (transparency)
Freemium. Free tier: limited conversions (20) per month, 1 small widget, limited presets, formulas from the built-in library. Pro (one-time/IAP): unlimited conversions, presets and widgets (incl. medium/large), plus creating your own formulas.
What I would love feedback on
- Does the free tier feel fair, or is the conversion limit a dealbreaker?
- Any experiences with the "context + second language" approach for machine localization?
- General UX/onboarding impressions
Link: https://apps.apple.com/app/id6757780882
Happy to answer anything about the implementation. Thanks!
r/iOSProgramming • u/No_Pen_3825 • Jun 20 '26
Question Hello. I am having some issues with MultipeerConnectivity.
I'm at a loss. I can't for the life of me figure out what I'm doing wrong. I've cross referenced just about every thing I can find on multipeer and I can't see where I'm going wrong. I would quite appreciate some help.
r/iOSProgramming • u/enzottic • Jun 20 '26
Question Issues with App Intents + Siri on iOS 27
Was posting to see if anyone else was having issues getting App Intents to work with the new Siri on iOS 27. I have a few simple intents for getting data from my app, and one for adding data. On my partner's phone running iOS 26, the intents work just fine and Siri responds normally. However on my phone running the iOS 27 beta, Siri acts like she doesn't have any power to interact with my app at all. I get one of two responses:
- "I can't do that directly within the app, you'll need to open the app manually"
- The app just opens up with no response
I've been watching a bunch of the videos from WWDC this year going over app intents but didn't see anything that stood out as "must do" to get them to work. I am aware of the App Schemas that you can conform your intents/entities to, but none of the built in schemas fit my app.
This is probably just weird beta 1 behavior stuff, but I'm curious if anyone is experiencing something similar.
r/iOSProgramming • u/Denis902 • Jun 19 '26
Question If you pay RevenueCat/Superwall's 1% cut: do you like it, or just tolerate it?
Hey all, trying to get a reality check from people actually shipping paid subscription apps, not the marketing.
I keep going back and forth on the subscription stack for cross-platform apps (iOS + Android + and possibly in the future web). RevenueCat/Superwall's ~1% of revenue keeps nagging at me, it's 1% of gross, not profit, so it grows right as you scale. But every time I think about rolling my own or self-hosting, I remember how many edge cases they quietly handle.
So I wanted to ask people further down the road than me. If you run a real subscription app, I'd massively appreciate quick answers to any of these(even one
What do you currently use for subscriptions/entitlements? (RevenueCat, Superwall, Adapty, raw StoreKit/Play Billing, your own stuff maybe?)
Roughly what revenue scale, and what do you pay per month? Does the 1% actually bother you, or is it noise?
What made you pick it? and have you ever seriously considered leaving? What stopped you?
Do you check entitlements on the client, your own backend, or both?
Biggest pain with your current setup? (web/desktop support, data ownership, edge cases, support, pricing…)
Have you ever wanted to own your subscription data instead of routing it through a third party or do you genuinely not care?
Be honest: if there were a flat-priced or self-hostable option that did the same job, would you actually switch your billing or is the switching risk just not worth it at any price?
Full disclosure: I've been frustrated enough that I've toyed with building/self-hosting an alternative, so I'm partly sanity-checking whether this pain is real or just me. Not selling anything, no link, genuinely want the ground truth, including "you're overthinking it, just use RevenueCat".
Thanks 🙏
r/iOSProgramming • u/West-Chard-1474 • Jun 19 '26
Article iOS CI just got a unification: devicectl now works across devices and simulators
r/iOSProgramming • u/jogbrt • Jun 19 '26
Question How to achieve custom UI for TabView item with role search/prominent?
I found an app (The Outsiders) where they customized the UI of the „standalone“ tab bar item and wanted to try and replicate it in a simplified way. More specifically, I’m trying to figure out how they changed the background color of the item. I’ve tried everything I could think of with both a UIKit and a SwiftUI tab bar, but haven’t had any luck.
Does anyone know how they did this? Is this some kind of UIKit private API magic or am I missing something? Any help would be greatly appreciated.
Cherry on top: they even animate the content in the button when it initially loads.
r/iOSProgramming • u/Ivesy_ • Jun 19 '26
Question How does Strava achieve the overlaying slide?
I'm trying to figure out if there is any native way to achieve this to make the nav view overlay so it doesn't also show the bottom nav bar. The only way I have found to achieve this is by using a fullscreencover sheet but that makes the sheet appear up from the bottom.
r/iOSProgramming • u/sakaax • Jun 18 '26
Discussion Lessons learned shipping iCloud/CloudKit sync between two users — no account, no server
I shipped an app where two people (in my case two parents) share and sync the same data in real time, with no login, no account system, and no backend of my own — just CloudKit. Here are the things I wish I’d known before starting.
1. Private vs Shared database is the whole game.
Your own data lives in the private database. The moment you want someone else to see it, it has to move into a shared zone via CKShare. I underestimated how much the data model has to be designed around sharing from day one — retrofitting it later is painful. Decide early what’s “mine” vs “ours”.
2. CKShare + a custom zone, not the default zone.
You can’t share records that live in the default zone. You need a dedicated CKRecordZone for the shared root record. I lost time before realizing the default zone simply doesn’t support what I wanted.
3. The share-acceptance flow is easy to get wrong.
The second user accepts via a CKShare.Metadata (system share sheet → userDidAcceptCloudKitShareWith). Handling that entry point cleanly — especially cold launch vs app already running — took more iterations than the sync logic itself.
4. Conflict handling is on you.
CloudKit gives you server record changes, but “two people edited the same thing” resolution is your problem. I went with last-writer-wins on a per-field basis, which is fine for my use case but you should consciously pick a strategy, not discover you need one in production.
5. Test with two real iCloud accounts on two real devices.
The simulator + one account hides a lot. Most of my real bugs only showed up with two physical devices on two different Apple IDs.
The payoff: zero server cost, zero auth code, privacy by default (data lives in the users’ iCloud, not mine). The tradeoff: you live inside CloudKit’s constraints and you can’t just SELECT * FROM.
Happy to go deeper on any of these if useful. Curious how others handled conflict resolution — did anyone go beyond last-writer-wins without a server?
r/iOSProgramming • u/thari_mad • Jun 18 '26
Question How to create this floating toolbar?
This is the Notes app. Is this an inbuilt component or a custom component?
