
[{"content":"","date":"29 October 2026","externalUrl":null,"permalink":"/tags/ai/","section":"Tags","summary":"","title":"AI","type":"tags"},{"content":"","date":"29 October 2026","externalUrl":null,"permalink":"/tags/career/","section":"Tags","summary":"","title":"Career","type":"tags"},{"content":"","date":"29 October 2026","externalUrl":null,"permalink":"/tags/job-market/","section":"Tags","summary":"","title":"Job Market","type":"tags"},{"content":"","date":"29 October 2026","externalUrl":null,"permalink":"/post/","section":"Posts","summary":"","title":"Posts","type":"post"},{"content":"","date":"29 October 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"Once a week my feed serves up another prediction: in a year or two AI will replace programmers, juniors are obsolete, learning to code is pointless. The authors change, the arguments don\u0026rsquo;t. Someone shows an agent building an app in an evening, someone quotes a CEO promising investors to \u0026ldquo;do more with fewer people\u0026rdquo;, and a thread about the end of the profession is ready. The hype crowd keeps pouring the same three ideas from one glass into another, and every round they sound more certain.\nI look at this from two sides. I have been writing iOS apps for more than eight years, I\u0026rsquo;m job hunting right now, and I see the market from the inside: openings, interviews, requirements. I also invest, and I read the quarterly reports of the very companies whose executives hand out those loud forecasts. Put a layoff press release next to the shareholder report and the picture turns out far more down to earth than in the threads.\nMy position is simple: AI is not replacing developers. The layoffs come down to money. First, the cheap money that companies used to hire people they didn\u0026rsquo;t need in 2020–2021 ran out. Now money is also flowing into AI hardware, and part of the bill is paid out of payroll. AI is a convenient signboard in this story: telling investors \u0026ldquo;we\u0026rsquo;re reorganizing around a new technology\u0026rdquo; is far more pleasant than \u0026ldquo;we got our headcount planning wrong\u0026rdquo;.\nThat doesn\u0026rsquo;t mean I\u0026rsquo;m ready to claim AI changed nothing. It changed a lot, just not where the hype crowd is looking. It isn\u0026rsquo;t the main reason for how many developers are being hired, but it noticeably changes what kind of developer is needed. And that hits hardest the people who are only trying to get into the profession.\nBelow I take both parts in order. First, what actually happened to the market and what the reports say. Then, what changed in the work itself and why the entry bar keeps creeping up.\nWhat actually happened to the market # A party on cheap money # In 2020–2021 the Fed\u0026rsquo;s rate sat near zero, everyone was at home living inside apps, and investors paid for growth and weren\u0026rsquo;t too curious about profit. Companies hired ahead of demand, on plans in which the pandemic surge in digital services became the new normal. Indeed\u0026rsquo;s index of software development job postings (February 2020 = 100) reached 234 at the end of February 2022: employers were looking for developers 2.3 times as actively as before the pandemic (FRED / Indeed Hiring Lab).\nThe companies themselves don\u0026rsquo;t deny the overhiring. In November 2022 Zuckerberg announced a cut of 13% of Meta\u0026rsquo;s staff, more than 11,000 people, and wrote it plainly: \u0026ldquo;I got this wrong, and I take responsibility.\u0026rdquo; He had expected the pandemic growth to last, and it didn\u0026rsquo;t (Fortune).\nMoney got expensive # In March 2022 the Fed started raising rates, and by summer 2023 it had brought them to 5.25–5.5%. The investors\u0026rsquo; question \u0026ldquo;how fast are you growing?\u0026rdquo; turned into \u0026ldquo;when will you start making money?\u0026rdquo;. And the easiest place to save is the biggest line in an IT company\u0026rsquo;s budget: people.\nDev job postings fell with the rate hikes, not after ChatGPT Software development job postings on Indeed (US), Feb 1, 2020 = 100 Effective federal funds rate, monthly average Source: Indeed Hiring Lab and Federal Reserve Bank of St. Louis, FRED series IHLIDXUSTPSOFTDEVE and FEDFUNDS Look at the dates. Postings began to fall in spring 2022, right after the first rate hike. The index dropped below its pre-pandemic level in spring 2023 and hit bottom, around 61, only in May 2025. ChatGPT came out on November 30, 2022, when the decline was already six months old, and back then replacing even one developer with it would have been a very bold idea. Now, at the end of October 2026, there are still about 19% fewer postings than before the pandemic.\nFrom the inside it looks like this. In three or four years the market went from active hiring to a sluggish, stale state. It used to be common that a couple of technical screens and a chat with the hiring manager were enough for an offer. Now a recruiter call can easily be followed by two technical screens, a call with the manager, and then a full onsite panel. The data shows the same from the other side: according to Ashby, hiring one person into a technical role now takes interviewing 17.6 candidates, versus 11.6 in 2021 (Ashby). And a noticeable share of openings seems to exist only for show: by Greenhouse\u0026rsquo;s estimate, in one quarter of 2024, 18–22% of postings on its platform were \u0026ldquo;ghost jobs\u0026rdquo; that nobody was hiring for (FOX 29).\nAI as a signboard # Against this background AI turned out to be a very convenient explanation. In January 2026 Oxford Economics calculated that in the US AI was cited in roughly 55,000 layoffs over the first 11 months of 2025. That is about 4.5% of all announced layoffs, while \u0026ldquo;market and economic conditions\u0026rdquo; accounted for around 245,000, four times as many. The analysts say outright that they suspect companies of repackaging ordinary layoffs as a story about a technological pivot (Fortune). Molly Kinder of the Brookings Institution called such an explanation a \u0026ldquo;very investor-friendly message\u0026rdquo; (TechCrunch). The phenomenon already has a name: AI-washing.\nWhat the reports show # Let\u0026rsquo;s test the hype crowd\u0026rsquo;s version with simple logic. If AI replaced developers, two things should show up in the reports at once: headcount shrinking, and productivity and margins growing like crazy. The costs are gone, the output stayed.\nProfit did grow after layoffs, but not because of AI. In the fourth quarter of 2023 Meta tripled its profit with 22% fewer staff than a year earlier, and Zuckerberg called 2023 the \u0026ldquo;year of efficiency\u0026rdquo; (TechXplore). Agents that write code didn\u0026rsquo;t exist yet. Profit grew because payroll was cut and the ad market rebounded.\nAn explosion in productivity, on the other hand, isn\u0026rsquo;t there. According to the same Oxford Economics, labor productivity growth is slowing, not accelerating. In the MIT NANDA report (2025), about 95% of corporate generative AI pilots produced no measurable effect on profit (Fortune). In a METR study, experienced developers using AI tools solved tasks 19% slower, even though they were sure they had sped up (The Decoder).\nWhen I open annual reports, the first things I look at are headcount, capex, free cash flow and debt. And on those lines the picture is nothing like the press releases.\n--- config: themeVariables: xyChart: backgroundColor: \u0026#34;transparent\u0026#34; plotColorPalette: \u0026#34;#eb6834, #2a78d6\u0026#34; --- xychart-beta title \u0026#34;Meta + Alphabet + Microsoft: capex and headcount (2019 = 100)\u0026#34; x-axis [2019, 2020, 2021, 2022, 2023, 2024, 2025] y-axis \u0026#34;index\u0026#34; 0 --\u0026gt; 450 line \u0026#34;Capex\u0026#34; [100, 100, 121, 165, 167, 255, 429] line \u0026#34;Headcount\u0026#34; [100, 116, 133, 162, 153, 158, 162] Orange line: capex. Blue line: number of employees. Source: 10-K annual reports; Microsoft\u0026rsquo;s fiscal year ends June 30.\nTake Meta, Alphabet and Microsoft together. In 2022 they employed 497,700 people. In 2025, again 497,700: headcount returned exactly to the peak of the pandemic hiring, no more and no less. Meanwhile capex, the money for data centers and equipment, grew over the same period from $86.8 billion to $225.7 billion, 2.6 times (10-K annual reports, SEC). For 2026 Alphabet plans $175–185 billion and Meta $115–135 billion (Alphabet, Meta). They haven\u0026rsquo;t added people for four years, while the money for hardware grows many times over.\nThat money shows up on two other lines too. Amazon\u0026rsquo;s trailing twelve-month free cash flow fell to $1.2 billion in the first quarter of 2026, from roughly $26 billion a year earlier (The Next Web). And their own cash is no longer enough for the buildout: according to Dealogic, Alphabet, Amazon, Meta, Microsoft and Oracle issued $159 billion of bonds in the first five months of 2026, more than in 2020–2024 combined (24/7 Wall St.).\nThe buyers of AI have a line item of their own: tokens. Uber burned through its annual AI tooling budget for development in four months, largely on Claude Code, with employees nudged toward it by an internal ranking of teams by AI usage. Asked whether those tokens turn into more useful features for customers, Uber\u0026rsquo;s president and COO Andrew Macdonald answered: \u0026ldquo;That link is not there yet\u0026rdquo; (Fortune).\nA fresh example: Microsoft # While I was writing this article, Microsoft offered voluntary buyouts to about 60,000 employees, around 27% of its staff. Half a year earlier the company had launched an early retirement program and then laid off 4,800 people (Forbes). According to the annual report, in the fiscal year that ended in June 2026 headcount fell from 228,000 to 223,000, while spending on equipment grew from $64.6 billion to $115.9 billion (10-K, SEC). CNBC framed the buyout news like this: \u0026ldquo;as AI leads to efficiency push\u0026rdquo; (CNBC).\nI read it differently. AI really is behind part of the layoffs, just not the way the threads tell it. People aren\u0026rsquo;t being replaced by machines. People are being cut so there is something to pay for machines and tokens. For AI sellers like Microsoft itself, it\u0026rsquo;s a bet on future revenue. For the buyers, judging by Uber and the MIT report, tokens aren\u0026rsquo;t turning into profit yet.\nWhere AI really is involved # Saying AI has nothing to do with any of this would be as much of a stretch as shouting about the end of the profession. It has its own, quite specific segment: the bottom rung.\nStanford economists (Erik Brynjolfsson and co-authors) took payroll data from ADP, one of the largest payroll processors in the US, and looked at employment by age. In the occupations most exposed to AI, software development among them, employment of 22–25-year-olds in the first version of the paper (data through July 2025) fell by about 13% relative to other groups, and in the latest one, from August 2026, the gap reached 19%. For more experienced colleagues in the same occupations, employment held steady or grew (Canaries in the Coal Mine).\nMy first reaction was predictable: it\u0026rsquo;s interest rates, when money is expensive the young are hired last. The authors tested that version separately and found no confirmation: the occupations more exposed to AI are, on average, even less sensitive to rates (Stanford DEL). But the timing matters: under the strictest test the gap becomes statistically significant only from 2024. AI does not explain the main wave of layoffs in 2022–2023, but it explains well what is happening after it.\nAt the same time, AI is not killing demand for developers overall. Since the end of February 2025, when Claude Code came out, software development postings in the US on Indeed have grown by almost 15%, even though total postings fell by 7%. What matters is who exactly became needed: 71% of the increase came from senior positions (Indeed Hiring Lab).\nPut it all together and the picture is this. How many developers the market needs is decided by interest rates, investors and hardware budgets, and AI as a replacement for people plays a supporting role there. But which developers are needed is increasingly decided by AI. Experienced people who can steer a model are sought more actively. Those who have just arrived, less and less. Even when money gets cheaper and hiring revives, entering the profession won\u0026rsquo;t be what it was in 2019. Next, why.\nHow the work itself changed # For me this shift turned out to be more noticeable than any chart. Over the last year working with code has changed dramatically, and I see it from two sides: in my own side projects and in interviews.\nIn interviews, hiring managers increasingly admit themselves that their teams haven\u0026rsquo;t touched code by hand for months and only write prompts. Public statements sound the same. In February 2026 Spotify co-CEO Gustav Söderström said the company\u0026rsquo;s best developers \u0026ldquo;haven\u0026rsquo;t written a single line of code since December\u0026rdquo;: an engineer asks Claude to fix a bug in the iOS app on the way to the office, gets a build in Slack and merges it to production before arriving at work (TechCrunch). In spring 2026 Sundar Pichai said AI generates 75% of new code at Google, up from about 25% in 2024 (Semafor). Yes, these are CEO words aimed at investors, and after the AI-washing section I don\u0026rsquo;t advise taking them on faith. But on the main point they match what I see myself: less and less code is written by hand.\nInterviews have caught up. In my experience, about every second interview loop now includes a stage with AI: either writing code together with an assistant, or debugging. Meta started testing this format back in summer 2025 (TechRadar), and in May 2026 it became known that Google is launching a pilot in the second half of the year where candidates for junior and mid-level roles will be allowed to use Gemini in the round on reading and debugging someone else\u0026rsquo;s code (Business Insider via Yahoo Finance). I write about these rounds in more detail in a separate article.\nWhat does all this mean for everyday work? Before, most of the time went into translating a solution into code: syntax, APIs, boilerplate, documentation. Seniors found this routine the most draining: the solution has been clear for ages, and you still have two more days of typing it out. Now there is always a junior or a mid-level (depending on the model) at hand, and you can hand them the job of sketching all the boilerplate by the rules you set. While the model spits out tokens, you have time to think about the rules themselves.\nWhat stays with the human is everything around the code. Setting the task and gathering context. Breaking it into pieces the model can do reliably. Deciding how the architecture is laid out and where the module boundaries run. Reading and reviewing what the model wrote, verifying it with tests and debugging. And answering for the result, because the model doesn\u0026rsquo;t. In short, the developer turns from an executor into a tech lead over a very fast and very self-assured junior who sometimes lies.\nWhich skills come to the front # The main skill used to be \u0026ldquo;I can write it\u0026rdquo;. Now it\u0026rsquo;s \u0026ldquo;I know what it should look like before it\u0026rsquo;s written\u0026rdquo;. In side projects this is clearer to me now than ever. The most valuable thing I bring to working with a model is not typing speed but an idea of what the code should be: which patterns the business functionality maps onto, which architecture wraps the process, by which criteria I\u0026rsquo;ll accept the result.\nThat\u0026rsquo;s why before generating anything I get to grips with the domain myself. Not to write everything by hand, but to understand which implementation I want to see, and where its critical paths and failure points are. Without that I can neither set the task nor check the result.\nAt the general planning stage, by contrast, the model can be let off the leash. What if we build the process like this? What if on-device ML shows up later? Which SQLite wrapper to pick if the flows are reactive? How do we test this critical path? The model will sketch five options where I\u0026rsquo;d have stopped at two, and its answers can be turned this way and that like a seasoned data analyst would: compare them by criteria, ask it to criticize its own proposal. It is safe to let it loose here for a simple reason: nothing has reached the codebase yet, and a mistake costs one discarded idea.\nAs soon as the model starts writing code, the leash gets shorter. It doesn\u0026rsquo;t remember past sessions, so its memory becomes the documentation: what is being done, why, and which decisions led to a given approach. It also knows how to deceive itself. A test fails, and the model \u0026ldquo;fixes\u0026rdquo; the test instead of the code. That isn\u0026rsquo;t paranoia: the phenomenon has a name, reward hacking, and researchers at METR documented models tampering with checks so that any solution passes, even when explicitly asked not to cheat (METR). So I protect tests, documentation and git history technically, not with requests in the prompt. How exactly, with hooks, permission rules and their non-obvious holes, I\u0026rsquo;ll cover in a separate article.\nEverything listed (architectural vision, domain knowledge, acceptance criteria, supervision of the executor) used to be called the skills of a senior or a tech lead. Now it\u0026rsquo;s the baseline kit for working with a model. And this is where the main problem begins.\nWhy this doesn\u0026rsquo;t work without fundamentals # All these skills rest on one foundation: you understand what the code you didn\u0026rsquo;t write yourself does, and you notice what\u0026rsquo;s missing from it.\nYou can only review what you understand. A model will confidently produce a closure that captures self and quietly keeps a view controller in memory. Or update the UI off the main thread. Or pass an NSManagedObject from one Core Data context to another, and everything works right up to the first serious load. The code compiles, looks tidy, and the tests the model wrote for it itself are green.\nBut more often than errors in what the model wrote, I run into what it didn\u0026rsquo;t write. The model loses states. Picture working with any entity as a tree of options: data goes down the correct path, fails with an error, goes to retry, and at every step there may be a fork. In multi-step processes the model most often implements the happy path with confidence and drops the handling of the other branches.\nThat\u0026rsquo;s how it went in my document processing side project. There are many stages: import, recognition, analysis, user confirmation, event generation. And each stage breaks down into substeps. Import alone requires saving the file to disk, writing it to the database and generating a preview.\nDiagram: a document\u0026#39;s path through import and the branches the model loses flowchart TD A[\u0026#34;User picks a document\u0026#34;] --\u0026gt; B[\u0026#34;Save file to disk\u0026#34;] B --\u0026gt;|ok| C[\u0026#34;Write record to database\u0026#34;] C --\u0026gt;|ok| D[\u0026#34;Generate preview\u0026#34;] D --\u0026gt;|ok| F[\u0026#34;Document ready for recognition\u0026#34;] F --\u0026gt; G[\u0026#34;Recognition\u0026#34;] G --\u0026gt;|ok| H[\u0026#34;Analysis → confirmation → events\u0026#34;] B -.-\u0026gt;|\u0026#34;no space, I/O error\u0026#34;| E1[\u0026#34;Report the error,\u0026lt;br/\u0026gt;save nothing\u0026#34;] C -.-\u0026gt;|\u0026#34;database error\u0026#34;| E2[\u0026#34;Delete the file from disk,\u0026lt;br/\u0026gt;roll back the import\u0026#34;] D -.-\u0026gt;|\u0026#34;preview failed\u0026#34;| E3[\u0026#34;Show a placeholder,\u0026lt;br/\u0026gt;retry in background\u0026#34;] G -.-\u0026gt;|\u0026#34;app was closed\u0026#34;| E4[\u0026#34;Save progress,\u0026lt;br/\u0026gt;resume on launch\u0026#34;] classDef happy fill:#2a78d6,stroke:#2a78d6,color:#ffffff classDef lost fill:#fcfcfb,stroke:#eb6834,stroke-width:2px,stroke-dasharray:5 4,color:#0b0b0b class A,B,C,D,F,G,H happy class E1,E2,E3,E4 lost Blue is the path the model implements eagerly. Dashed are the branches it loses unless you spell them out.\nWhat happens if the file was written to disk but not to the database? If the preview failed to generate? If the user closed the app in the middle of recognition? Unless you walk the model through every step and every branch, it will most likely just skip them. The code still looks finished. A skipped branch isn\u0026rsquo;t visible in review; you have to know about it in advance.\nWhere does that knowledge come from? Not from documentation. My instinct for this kind of thing grew at a previous job, where I worked on complex messenger functionality. Even ordinary messages travel a long way: load, check for overlaps with what already exists, filter, write to the database, read the right slice back from the database, make sure the data layer has no duplicates, and only then show it. An error at any step turns into a lost or doubled message in a real person\u0026rsquo;s chat.\nCalls are even more fun. I wrote them from scratch and maintained them alone. You are juggling states and events from CallKit, AVAudioSession and the calling library (LiveKit in my case), the user database for conferences, interactive events from the socket, and media streams all at once. Each of these systems lives its own life and can change state at any moment. After a school like that, the question \u0026ldquo;what happens if everything goes wrong right here?\u0026rdquo; asks itself. The model doesn\u0026rsquo;t ask it. You do, if you know what to ask.\nThat\u0026rsquo;s how the \u0026ldquo;something is off here\u0026rdquo; instinct accumulates: from hundreds of small mistakes made by hand, from the crash you chased until two in the morning, from the leak you found in Instruments a week after release.\nThere is data too. In early 2026 Anthropic ran a randomized experiment: 52 developers, mostly juniors, learned Trio, a Python async library new to them. Half worked with an AI assistant, half without. On the final test of the material the AI group scored 50% on average, the group without AI 67%. The gap was widest on debugging questions, which is exactly the skill that becomes central when working with a model. Meanwhile the AI group finished only a couple of minutes faster, and that difference is not statistically significant (Anthropic).\nThere is an encouraging detail in the study as well. Participants who asked the AI conceptual questions or had it explain generated code learned with almost no loss: 65% or higher on average. The worst results, below 40%, came from those who delegated the task entirely. The groups are small, and the authors call this part of the findings correlational. But the direction is clear: AI hurts learning when it works instead of you, and barely hurts it when it works as a teacher.\nThey pulled the ladder up from below # Now the main paradox. The fundamentals without which the skills from the previous sections don\u0026rsquo;t work used to be built on quite specific tasks. And those are exactly the tasks a model handles best.\nWhen I came into development, for the first six months, maybe a year, I did tasks of this kind: figure out why a view has the wrong color, why a button doesn\u0026rsquo;t respond to taps, why a collection shows the wrong cell. A language model now finds and fixes this in no time. But it is on these tasks that the foundation gets laid. Not the theoretical one, which you can get from books, but the understanding of how theory lands on practice. The button doesn\u0026rsquo;t respond because a transparent view lies on top of it or the parent has isUserInteractionEnabled turned off, and while you hunt for that, you truly work out for the first time how hit-testing works. The collection shows the wrong cell because cells are reused and you didn\u0026rsquo;t reset the state of the previous one, and that\u0026rsquo;s how you learn about reuse: not from an article but from your own bug.\nThe market, meanwhile, says it outright: such tasks are no longer for a developer. How is a junior supposed to force themselves to do by hand what a model will do from one short prompt? And watching someone else solve the problem is nothing like struggling with it for half a day, digging through documentation and figuring out how it works under the hood. That \u0026ldquo;struggle\u0026rdquo; even has a scientific name: psychologists Robert and Elizabeth Bjork of UCLA call such conditions desirable difficulties. Learning this way is harder and slower, but the knowledge sticks far more firmly than after skimming someone else\u0026rsquo;s solution (APS Observer).\nNow add expensive money. Companies used to take juniors on for growth, hand them that very stream of small bugs and wait for them to grow into mid-levels. It was an investment with a delayed payoff, and at zero rates you could afford it. With expensive money the payoff is needed now, and an agent closes small bugs faster and cheaper than a junior. According to SignalFire, in 2024 new graduates made up just 7% of hires at the 15 biggest tech companies, and their hiring fell by more than half compared with 2019 (SignalFire).\nThat\u0026rsquo;s the whole construction. Expensive money discouraged companies from investing in training. AI removed the tasks people learned on. Each factor alone could still be lived with. Together they took the ladder apart from the bottom: the upper rungs are in place, and that\u0026rsquo;s where people are being sought (remember, 71% of posting growth was for seniors), while the first rungs are gone.\nWhat to do about it # First, the analogy most often brought up in AI debates. Compilers, frameworks and Stack Overflow also took part of the manual work away from developers, and each time someone buried the profession. I mostly agree with that analogy. It\u0026rsquo;s just that the conclusion usually drawn from it is the wrong one.\nLook at who is still valued the highest: people who understand what happens under all the layers of abstraction. In the late 1990s Chris Sawyer wrote RollerCoaster Tycoon 99% in x86 assembly (Wikipedia). Chris Lattner started LLVM as a graduate student, then built Clang and Swift at Apple, then created Mojo, a language for AI development, at his company Modular, and now leads advanced AI and platforms at Qualcomm (Lattner\u0026rsquo;s résumé). The knowledge of people like this goes very deep into the domain, with a cultural layer of modern advances grown on top. As a result they have both a deep view of the essence of things and a wide view of a large slice of the industry. Abstractions did not cancel the value of understanding what lies beneath them. They made that understanding rare, and therefore expensive.\nWith AI it\u0026rsquo;s the same story, only the abstraction layer is thicker and the temptation not to look beneath it is stronger.\nFor those entering the profession # If a junior asked me where to start, I\u0026rsquo;d answer like this: don\u0026rsquo;t collect recipes, develop an approach. Specific tasks will keep changing, and a model will solve any of them faster than you. What\u0026rsquo;s valuable isn\u0026rsquo;t the answer to \u0026ldquo;why doesn\u0026rsquo;t the button respond\u0026rdquo;, but the ability to find the cause yourself, propose a solution and defend it. In front of a person or in front of a model, it doesn\u0026rsquo;t matter.\nThe rest follows from that. Before asking the model, understand the domain yourself: what the implementation should look like, where its critical paths are, what can fail. Use the model as a teacher, not as an executor: the Anthropic study above shows that those who asked \u0026ldquo;why\u0026rdquo; learned almost without loss. Fix bugs by hand in at least one project where agents are banned. And dig one layer deeper than your tool requires. If you write in SwiftUI, learn UIKit, which it is still closely entangled with. If you work with SwiftData, look into Core Data. If the model proposed a solution with actors, understand what they guarantee and what they don\u0026rsquo;t.\nFor teams and tech leads # When I onboarded juniors, an iterative approach worked best, and it\u0026rsquo;s strikingly similar to working with a model. You set a plan with concrete problems. The junior goes off, looks for causes and proposes solutions. You discuss their reasoning: either you accept it or steer it the right way. That\u0026rsquo;s how you gradually walk them through all the layers and functionality they will be working with. The point isn\u0026rsquo;t to give tours of the codebase, but for the junior to develop an approach to varied tasks. The tasks shouldn\u0026rsquo;t become familiar, and the approach should crystallize regardless of the problem domain.\nNow it\u0026rsquo;s worth doing this on purpose, because on its own it no longer happens. Leave juniors some tasks \u0026ldquo;by hand\u0026rdquo; as an investment, not as an inefficiency. Yes, an agent will do it faster. But in two years you\u0026rsquo;ll need a mid-level who understands your codebase, not a person who knows how to press \u0026ldquo;accept\u0026rdquo;. And review not just the code but the understanding: the question \u0026ldquo;explain why it\u0026rsquo;s done this way\u0026rdquo; separates someone who figured it out from someone who simply accepted the model\u0026rsquo;s suggestion.\nConclusion # Back to the threads from the beginning. Developers are being cut not because AI replaced them. They are being cut because cheap money ended while the headcount hired with it stayed, and because money now goes to AI hardware. In this story AI is a convenient signboard for investors and a line item that justifies cutting payroll.\nBut AI does change the profession, just in a different place. It cuts not people but the rungs people used to climb. Entry into the profession shifts from \u0026ldquo;I can write code\u0026rdquo; to \u0026ldquo;I can think about a system and verify the result\u0026rdquo;, and that shift will stay even when money gets cheap again.\nThe fundamentals haven\u0026rsquo;t gone anywhere. It\u0026rsquo;s just that nobody will let you build them on the job anymore. You\u0026rsquo;ll have to do it yourself.\nAnd the next time another thread about the end of the profession lands in your feed, look at the dates, at the reports and at the Fed rate. The picture usually turns out far more down to earth.\nSources # Full list of sources (about 35 links) Market and hiring\nSoftware Development Job Postings on Indeed in the United States and Federal Funds Effective Rate, FRED Mark Zuckerberg takes the blame for failing to spot the post-pandemic downturn, Fortune, November 2022 AI and Job Postings: From Destruction to Creation?, Indeed Hiring Lab, July 2026 Talent Trends Report: Recruiter Productivity, Ashby, 2026 1 in 5 job postings aren\u0026rsquo;t real, recent study finds, FOX 29, January 2025 The SignalFire State of Tech Talent Report 2025, SignalFire, May 2025 AI, layoffs and reports\nAI layoffs: convenient corporate fiction? (Oxford Economics), Fortune, January 2026 AI layoffs or \u0026lsquo;AI-washing\u0026rsquo;?, TechCrunch, February 2026 Meta posts sharp profit, revenue increase in Q4 thanks to cost cuts, TechXplore, February 2024 MIT report: 95% of generative AI pilots at companies are failing, Fortune, August 2025 AI coding can make developers slower even if they feel faster (METR), The Decoder, July 2025 Capex from 10-K annual reports (XBRL, SEC): Meta, Alphabet, Microsoft Headcount from annual reports: Meta, Alphabet, Microsoft, StockAnalysis (based on SEC data) Meta Q4 and Full Year 2025 Results and Alphabet Q4 and Fiscal Year 2025 Results Big Tech AI capex and free cash flow pressure, The Next Web, July 2026 In 5 Months, Big Tech Has Borrowed More Than In the Last 5 Years, 24/7 Wall St. (Dealogic data), June 2026 Uber COO on AI spending, tokens and Claude Code, Fortune, May 2026 Microsoft offers buyouts to 60,000 staffers, Forbes, October 2026 Microsoft offers voluntary buyouts to 60,000 employees as AI leads to efficiency push, CNBC, October 2026 AI and early-career developers\nCanaries in the Coal Mine? Six Facts about the Recent Employment Effects of AI, Brynjolfsson, Chandar, Chen, Stanford Digital Economy Lab (August 2026 version) Canaries, interest rates and timing, Stanford Digital Economy Lab, February 2026 How AI assistance impacts the formation of coding skills, Anthropic, January 2026 Desirable Difficulties, APS Observer, 2016 How the work and interviews are changing\nSpotify says its best developers haven\u0026rsquo;t written a line of code since December, TechCrunch, February 2026 Google CEO says 75% of company\u0026rsquo;s new code is AI-generated, Semafor, April 2026 Meta is going to let candidates use AI in job interviews, TechRadar, July 2025 Google will let candidates use AI in interviews, Moneywise / Yahoo Finance, citing Business Insider, May 2026 Recent Frontier Models Are Reward Hacking, METR, June 2025 People\nRollerCoaster Tycoon, Wikipedia Chris Lattner: résumé ","date":"29 October 2026","externalUrl":null,"permalink":"/post/ai-entry-barrier/","section":"Posts","summary":"Once a week my feed serves up another prediction: in a year or two AI will replace programmers, juniors are obsolete, learning to code is pointless. The authors change, the arguments don’t. Someone shows an agent building an app in an evening, someone quotes a CEO promising investors to “do more with fewer people”, and a thread about the end of the profession is ready. The hype crowd keeps pouring the same three ideas from one glass into another, and every round they sound more certain.\n","title":"They Pulled the Ladder Up from Below: Why Breaking Into Software Is Harder with AI","type":"post"},{"content":"","date":"8 July 2025","externalUrl":null,"permalink":"/tags/uikit/","section":"Tags","summary":"","title":"UIKit","type":"tags"},{"content":"A simple way to preview your UIKit views without running the whole app.\nWe recently started modularizing our app at work. One of the first steps was moving most of our reusable UI components (buttons, input field combinations, loading indicators) into a separate Swift Package. That package is now included in our main app as a dependency.\nIt was the right move.\nNot only does this improve build times, simplify dependencies, and encourage better separation of concerns, but it also makes testing and reusing UI components across different modules a breeze. It’s much easier to isolate, maintain, and reason about UI logic when it’s decoupled from the rest of the app.\nBut there’s a small catch.\nSince the Swift Package has no app target and doesn’t get compiled into an actual host runtime (no @main, no SceneDelegate, nothing), there\u0026rsquo;s no way to \u0026ldquo;run\u0026rdquo; views inside it unless they\u0026rsquo;re bridged into an executable target.\nNow, our project has been increasingly adopting SwiftUI, and with that came a wonderful luxury: SwiftUI previews. Being able to instantly visualize and interact with UI in Xcode Previews has been a huge help.\nSo I thought: why not do the same with UIKit?\nTurns out, you can. Thanks to #Preview and a little bit of boilerplate, it’s entirely possible to make your UIView and UIViewController components previewable, and the speed boost is very real.\nLet’s start with a UIView # Let’s say you’ve got a custom button:\npublic final class MyFancyButton: UIButton { override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .systemBlue setTitle(\u0026#34;Tap Me\u0026#34;, for: .normal) layer.cornerRadius = 8 } @available(*, unavailable) required init?(coder: NSCoder) { fatalError(\u0026#34;init(coder:) has not been implemented\u0026#34;) } } To preview this in SwiftUI:\n#Preview { UIViewPreview { MyFancyButton() } .frame(width: 65, height: 34) .previewLayout(.sizeThatFits) } With the help of a tiny helper:\nstruct UIViewPreview\u0026lt;View: UIView\u0026gt;: UIViewRepresentable { private let viewBuilder: () -\u0026gt; View init(_ builder: @escaping () -\u0026gt; View) { self.viewBuilder = builder } func makeUIView(context: Context) -\u0026gt; View { viewBuilder() } func updateUIView(_ uiView: View, context: Context) {} } That gives instant visual feedback inside Xcode. # What about UIViewController? # No problem. Here\u0026rsquo;s a reusable wrapper:\nstruct ViewControllerPreview\u0026lt;ViewController: UIViewController\u0026gt;: UIViewControllerRepresentable { let builder: () -\u0026gt; ViewController init(_ builder: @escaping () -\u0026gt; ViewController) { self.builder = builder } func makeUIViewController(context: Context) -\u0026gt; ViewController { builder() } func updateUIViewController(_ uiViewController: ViewController, context: Context) {} } Note: we’re not using @autoclosure here: you’ll often want to pass actual closures that construct your controller with custom data.\nAnd then:\n#Preview { ViewControllerPreview { MyCustomViewController() } } This trick works beautifully for prototyping. You can inject mock data, test layout edge cases, preview different themes, all without having to run the full app.\nGotchas # Don’t expect Previews to support animations or real-time interactions. UIKit just sits there like a mannequin. If your preview crashes, check if your view/controller requires data. Previews won’t magically inject dependencies. Xcode previews sometimes hang for no reason. Close and reopen the canvas. Or restart Xcode. Or do a rain dance. No one knows why. Final thoughts # Using #Preview for UIKit is a real productivity tool, especially if you’re refactoring legacy code or slowly transitioning to SwiftUI. You don’t need to go full SwiftUI overnight to benefit from previews.\nStay tuned, or don’t. I won’t judge.\n","date":"8 July 2025","externalUrl":null,"permalink":"/post/uikit-previews/","section":"Posts","summary":"A simple way to preview your UIKit views without running the whole app.\n","title":"Using Preview for UIKit Views and ViewControllers","type":"post"},{"content":"","date":"17 June 2025","externalUrl":null,"permalink":"/tags/swift/","section":"Tags","summary":"","title":"Swift","type":"tags"},{"content":"InlineArray is a fixed-size array that keeps its elements inline, with no separate heap allocation. What it can do, what it can\u0026rsquo;t, and when it is worth using.\nWhat InlineArray is # InlineArray arrived in Swift 6.2 (SE-0453). Apple\u0026rsquo;s documentation describes it as a container that doesn\u0026rsquo;t use a separate memory allocation just to store its elements. The size is part of the type, so it is fixed at compile time:\n@frozen struct InlineArray\u0026lt;let count : Int, Element\u0026gt; where Element : ~Copyable Note the order of the generic parameters: the count comes first, then the element type. InlineArray\u0026lt;3, Int\u0026gt; is correct, InlineArray\u0026lt;Int, 3\u0026gt; is not. There is also a shorthand, [3 of Int].\nIt is available on iOS 26 and the matching releases of the other Apple platforms, and needs the Swift 6.2 compiler that ships with Xcode 26.\nCreating one # You can use an array literal, and let the compiler infer the count, the element type, or both:\nlet a: InlineArray\u0026lt;3, Int\u0026gt; = [1, 2, 3] let b: InlineArray\u0026lt;_, Int\u0026gt; = [1, 2, 3] let c: InlineArray\u0026lt;3, _\u0026gt; = [1, 2, 3] let d: InlineArray = [1, 2, 3] let e: [3 of Int] = [1, 2, 3] All elements must be initialized up front, and the literal must have exactly as many elements as the type says. Otherwise the compiler reports an error like expected '2' elements in InlineArray literal, but got '3'.\nA literal creates an InlineArray only when the compiler knows that this is the target type. Without that context, let x = [1, 2, 3] is a regular Array.\nThere are also initializers. init(repeating:) fills every slot with a copy of one value, and the closure initializer calls your closure with each index:\nlet zeros = InlineArray\u0026lt;4, Int\u0026gt;(repeating: 0) let squares = InlineArray\u0026lt;4, Int\u0026gt; { index in index * index } What it can\u0026rsquo;t do # This is where InlineArray differs most from Array. The size never changes. You can modify elements in place through the subscript, but you can\u0026rsquo;t append or remove, and you can\u0026rsquo;t assign an array to one of a different size. This sample is from the WWDC25 session on memory and performance:\nvar array: InlineArray\u0026lt;3, Int\u0026gt; = [1, 2, 3] array[0] = 4 // array == [4, 2, 3] // Can\u0026#39;t append or remove elements array.append(4) // error: Value of type \u0026#39;InlineArray\u0026lt;3, Int\u0026gt;\u0026#39; has no member \u0026#39;append\u0026#39; // Can only assign to a same-sized inline array let bigger: InlineArray\u0026lt;6, Int\u0026gt; = array // error: Cannot assign value of type \u0026#39;InlineArray\u0026lt;3, Int\u0026gt;\u0026#39; to type \u0026#39;InlineArray\u0026lt;6, Int\u0026gt;\u0026#39; There is no fallback to the heap when it \u0026ldquo;gets full\u0026rdquo;, because it can\u0026rsquo;t get full. SE-0453 states that InlineArray never introduces an implicit heap allocation just for its own storage.\nSE-0453 also says it doesn\u0026rsquo;t conform to Sequence or Collection, so the usual collection algorithms don\u0026rsquo;t come with it. The documentation\u0026rsquo;s way to walk over the elements is through the indices:\nvar values: [3 of Double] = [1, 1.5, 2] for index in values.indices { print(values[index]) } The subscript is bounds checked, and an invalid index is a runtime error.\nWhere the elements live # The elements are stored contiguously. According to the documentation, an InlineArray that is a stored property of a class lives in the heap object together with the other properties. In other cases it is, in general, allocated on the stack.\nSize and stride equal the element\u0026rsquo;s stride multiplied by the count, and alignment equals the element\u0026rsquo;s alignment. The documentation gives this example:\nstruct Record { let x: UInt32 let y: Bool } MemoryLayout\u0026lt;Record\u0026gt;.size // 5 MemoryLayout\u0026lt;Record\u0026gt;.stride // 8 MemoryLayout\u0026lt;Record\u0026gt;.alignment // 4 MemoryLayout\u0026lt;[2 of Record]\u0026gt;.size // 16 MemoryLayout\u0026lt;[2 of Record]\u0026gt;.stride // 16 MemoryLayout\u0026lt;[2 of Record]\u0026gt;.alignment // 4 Copies are eager # Array shares its buffer between copies and copies it lazily (copy-on-write). InlineArray doesn\u0026rsquo;t: every assignment copies all the elements right away, like a tuple.\nvar array: InlineArray\u0026lt;3, Int\u0026gt; = [1, 2, 3] var copy = array // copy happens on assignment for i in copy.indices { copy[i] += 10 } // array == [1, 2, 3] // copy == [11, 12, 13] The WWDC speaker calls this a double-edged sword. No copy-on-write means no reference counting for the storage and no uniqueness or exclusivity checks. But if your code copies the array a lot or shares it between several owners, you pay for each copy, and a large count makes each copy expensive.\nTwo more consequences, which the docs don\u0026rsquo;t state, so this is my own inference:\nLocals usually live on the stack, so I\u0026rsquo;d avoid a very large count. With an array of class references, a copy duplicates every reference, so I\u0026rsquo;d expect each element to be retained. If you store references, measure before assuming a win. A real use case from WWDC # In the session \u0026ldquo;Improve memory usage and performance with Swift\u0026rdquo; (WWDC25, session 312), Nate Cook speeds up a parser for the QOI image format. The parser keeps a pixel cache: an array of 64 RGBAPixel values that never changes size. The cache is modified in place and never copied, which is exactly the shape InlineArray fits.\nOne caveat about numbers. The session reports the parser becoming six times faster after the whole memory-management pass, but that pass also replaces Data with RawSpan and OutputSpan. There is no benchmark of InlineArray alone, so don\u0026rsquo;t read that figure as a gain from InlineArray.\nWorking with raw memory # InlineArray has span and mutableSpan properties, which give a Span or MutableSpan over the elements. Span is a safe replacement for buffer pointers: the compiler checks at compile time that the span doesn\u0026rsquo;t outlive the memory it points to. The type also conforms to ContiguousBytes.\nC interoperability is a different story. SE-0453 says C interop changes are deliberately postponed, and fixed-size C arrays still import into Swift as tuples.\nWhen to use it # Good fit:\nThe number of elements is known at compile time and never changes. The elements are modified in place and the array is rarely copied. You want to avoid a heap allocation, for example in a hot path. Bad fit:\nThe size varies at runtime, or you need append and remove. Use Array. The value is copied or shared a lot. As with any performance feature, the WWDC session starts with profiling in Instruments and only then changes the code. Do the same here.\nRelated # SE-0453: InlineArray, a fixed-size array InlineArray | Apple Developer Documentation WWDC25 Session 312: Improve memory usage and performance with Swift Swift 6.2 Released ","date":"17 June 2025","externalUrl":null,"permalink":"/post/swift-inline-array/","section":"Posts","summary":"InlineArray is a fixed-size array that keeps its elements inline, with no separate heap allocation. What it can do, what it can’t, and when it is worth using.\n","title":"Swift InlineArray","type":"post"},{"content":"","date":"3 June 2025","externalUrl":null,"permalink":"/tags/swiftui/","section":"Tags","summary":"","title":"SwiftUI","type":"tags"},{"content":"The guide on how to create modifier that adds zoom functionality to any SwiftUI View.\nProblem Statement # In modern iOS apps, it\u0026rsquo;s often necessary to let users zoom in on content: an image, a map, or any SwiftUI View. In this guide, we will build a reusable ViewModifier step by step to add support for:\nPinch-to-zoom Dragging Double-tap zoom The result is a ZoomableModifier and a zoomable(...) extension on View. Below, each part of the implementation is explained along with the SwiftUI and iOS details behind it.\nStep 1. Create the Base ViewModifier # Why use a ViewModifier?\nA ViewModifier lets you encapsulate complex view transformations or behavior in a reusable component. You can apply it via .modifier(...) or via a custom view extension.\nDefine the structure:\nimport SwiftUI struct ZoomableModifier: ViewModifier { let minZoomScale: CGFloat let maxZoomScale: CGFloat func body(content: Content) -\u0026gt; some View { // For now, just display the content content } } Explanation:\nminZoomScale and maxZoomScale let you configure the zoom limits. The body(content:) method returns the modified view. Step 2. Add State for Transform and Content Size # What state do we need?\ntransform (CGAffineTransform) for scale and translation. lastTransform to accumulate changes across gestures. contentSize to know the view’s real size via GeometryReader. Add properties:\n@State private var lastTransform: CGAffineTransform = .identity @State private var transform: CGAffineTransform = .identity @State private var contentSize: CGSize = .zero Why?\n@State lets SwiftUI track and animate changes. Initial values .identity and .zero start from a neutral state. Step 3. Capture the Actual Content Size # Using GeometryReader\nWrap the content in a transparent background to measure its size:\ncontent .background(alignment: .topLeading) { GeometryReader { proxy in Color.clear .onAppear { contentSize = proxy.size } } } Explanation:\nGeometryReader expands to match the content. proxy.size gives the content’s width and height. Store it in a @State variable for zoom calculations. Step 4. Apply Affine Transform to the View # Create a helper extension:\nprivate extension View { @ViewBuilder func animatableTransformEffect(_ transform: CGAffineTransform) -\u0026gt; some View { scaleEffect( x: transform.scaleX, y: transform.scaleY, anchor: .zero ) .offset(x: transform.tx, y: transform.ty) } } Why?\nscaleEffect and offset apply the transform visually. Anchoring at .zero (top-left) and then adjusting offset manually. Use in body:\ncontent .background { /* GeometryReader */ } .animatableTransformEffect(transform) Step 5. Implement Pinch Gestures for Zooming # iOS 16’s MagnificationGesture behaves differently than iOS 17+. We’ll support both.\n5.1 Old API (iOS 16) # @available(iOS, introduced: 16.0, deprecated: 17.0) private var oldMagnificationGesture: some Gesture { MagnificationGesture() .onChanged { value in let zoomFactor: CGFloat = 0.5 let scale = value * zoomFactor transform = lastTransform.scaledBy(x: scale, y: scale) } .onEnded { _ in onEndGesture() } } Here value grows as fingers spread, and we apply a linear scaling. 5.2 New API (iOS 17+) # @available(iOS 17.0, *) private var magnificationGesture: some Gesture { MagnifyGesture(minimumScaleDelta: 0) .onChanged { value in let newTransform = CGAffineTransform.anchoredScale( scale: value.magnification, anchor: value.startAnchor.scaledBy(contentSize) ) transform = lastTransform.concatenating(newTransform) } .onEnded { _ in withAnimation(.interactiveSpring()) { onEndGesture() } } } Key points:\nvalue.startAnchor tells where the pinch began. Convert UnitPoint to CGPoint by scaling with contentSize. Use concatenating to apply new transform onto the previous one. Step 6. Add Drag and Double-Tap Gestures # DragGesture for panning the zoomed content:\nprivate var dragGesture: some Gesture { DragGesture() .onChanged { value in withAnimation(.interactiveSpring) { transform = lastTransform.translatedBy( x: value.translation.width / transform.scaleX, y: value.translation.height / transform.scaleY ) } } .onEnded { _ in onEndGesture() } } Double-Tap to toggle between minimum and maximum zoom:\nprivate var doubleTapGesture: some Gesture { SpatialTapGesture(count: 2) .onEnded { value in let newTransform: CGAffineTransform = transform.isIdentity ? .anchoredScale(scale: maxZoomScale, anchor: value.location) : .identity withAnimation(.linear(duration: 0.15)) { transform = newTransform lastTransform = newTransform } } } Step 7. Limit Scale and Translation # Why?\nWithout constraints, users can zoom too little, too much, or pan content off-screen.\nThe limitTransform(_:) method:\nprivate func limitTransform(_ transform: CGAffineTransform) -\u0026gt; CGAffineTransform { let sx = transform.scaleX let sy = transform.scaleY // Constrain scale if sx \u0026lt; minZoomScale || sy \u0026lt; minZoomScale { return .identity } if sx \u0026gt; maxZoomScale || sy \u0026gt; maxZoomScale { let center = CGPoint(x: contentSize.width / 2, y: contentSize.height / 2) return .anchoredScale(scale: maxZoomScale, anchor: center) } // Compute max offsets let maxX = contentSize.width * (sx - 1) let maxY = contentSize.height * (sy - 1) // Clamp translation let clampedTx = (-maxX ... 0).clamp(transform.tx) let clampedTy = (-maxY ... 0).clamp(transform.ty) var t = transform t.tx = clampedTx; t.ty = clampedTy return t } Additional details:\nClosedRange\u0026lt;CGFloat\u0026gt;.clamp(_:) neatly clamps a value. In onEndGesture(), we apply limitTransform and animate snapping back into bounds. Step 8. Create a Convenient View Extension # Public modifiers:\npublic extension View { func zoomable( minZoomScale: CGFloat = 1, maxZoomScale: CGFloat = 3 ) -\u0026gt; some View { modifier(ZoomableModifier( minZoomScale: minZoomScale, maxZoomScale: maxZoomScale )) } func zoomable( minZoomScale: CGFloat = 1, maxZoomScale: CGFloat = 3, outOfBoundsColor: Color = .clear ) -\u0026gt; some View { GeometryReader { _ in ZStack { outOfBoundsColor self.zoomable( minZoomScale: minZoomScale, maxZoomScale: maxZoomScale ) } } } } Explanation:\nThe first method applies the modifier. The second wraps the content with a background outOfBoundsColor to fill empty areas during panning. Complete Code # The full final implementation can be found in my github by the link in the Related Links section. Copy it into your project and apply .zoomable() to any SwiftUI view or add as SPM package.\nConclusions # The modifier keeps the gesture state, the transform and the bounds logic in one place. The only things a caller configures are minZoomScale and maxZoomScale.\nApply .zoomable() to an image, a map, or any other SwiftUI view.\nRelated Links # GitHub link to repo ","date":"3 June 2025","externalUrl":null,"permalink":"/post/zoomable-swiftui-view-modifier/","section":"Posts","summary":"The guide on how to create modifier that adds zoom functionality to any SwiftUI View.\n","title":"Zoomable SwiftUI View Modifier","type":"post"},{"content":"The short guide on building a reusable SwiftUI button with styles, sizes, shapes and a loading state.\nIntro # In a SwiftUI project with a design system, the button is one of the first components worth making reusable. It looks simple, but in practice it often needs:\nSupport for different styles (primary, secondary, outline, text), Sizes (small, medium, large), Shapes (rounded, capsule), Proper visual response to states (normal, pressed, disabled), Ability to show a loading indicator. Why not just use .Button? # Of course, SwiftUI already provides a Button, and it can be customized manually. But as your project grows and you have dozens or hundreds of buttons across screens:\nManual configuration leads to code duplication, Inconsistent design appears, It becomes hard to apply global updates to look or behavior. That\u0026rsquo;s why we need a unified reusable component based on the ButtonStyle protocol, which allows us to:\nStandardize button appearance, Handle different states (enabled, pressed, loading), Be easily configurable through parameters like style, size, shape, etc. Goal of this guide # In this guide, we will build a universal button in SwiftUI using the ButtonStyle approach. Our goal is to gradually build a component that:\nSupports multiple visual styles and states, Can be applied via .regularButtonStyle(...), Can be adapted to any design system, Is suitable for open reuse across projects. How the guide is structured # We won\u0026rsquo;t jump straight to the final version. Instead, we’ll go step-by-step, adding one feature at a time:\nCreate a basic ButtonStyle Add styling for different states Add size support Implement button shapes Handle isPressed and isEnabled Add isLoading support Build a convenient .regularButtonStyle(...) modifier Set up a preview to test and demo all variations 2. Basic ButtonStyle # What is ButtonStyle? # In SwiftUI, the ButtonStyle protocol allows customizing the appearance and behavior of buttons. Instead of manually wrapping every Button in background, padding, cornerRadius, etc., you can define a unified style that applies automatically.\nIt’s similar to CSS styles: define once, reuse everywhere.\nBasic usage example:\nButton(\u0026#34;Click me\u0026#34;) { print(\u0026#34;Clicked\u0026#34;) } .buttonStyle(MyCustomButtonStyle()) Creating the basic RegularButtonStyle # Let’s start with the simplest version, no configuration yet, just to understand the structure.\nimport SwiftUI struct RegularButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -\u0026gt; some View { configuration.label .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(8) } } configuration.label is the content of the button (text, icon, etc.) makeBody(configuration:) is called every time the button state changes (e.g., on press).\nUsage:\nButton(\u0026#34;Basic Button\u0026#34;) { print(\u0026#34;Clicked\u0026#34;) } .buttonStyle(RegularButtonStyle()) Reacting to presses # The configuration includes an isPressed property that we can use to change the appearance:\nstruct RegularButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -\u0026gt; some View { configuration.label .padding() .background(configuration.isPressed ? Color.gray : Color.blue) .foregroundColor(.white) .cornerRadius(8) .animation(.easeInOut, value: configuration.isPressed) } } Now the button responds visually when pressed.\nFirst step toward reusability # To make the style configurable and reusable, we add parameters:\nstruct RegularButtonStyle: ButtonStyle { let backgroundColor: Color let foregroundColor: Color let cornerRadius: CGFloat func makeBody(configuration: Configuration) -\u0026gt; some View { configuration.label .padding() .background(configuration.isPressed ? backgroundColor.opacity(0.7) : backgroundColor) .foregroundColor(foregroundColor) .cornerRadius(cornerRadius) } } Usage example:\nButton(\u0026#34;Primary\u0026#34;) {} .buttonStyle( RegularButtonStyle( backgroundColor: .blue, foregroundColor: .white, cornerRadius: 12 ) ) 3. Styling for Different Button States # Why are states important? # A button in an app is not just a rectangle with text. It can be:\nActive (normal): ready to be tapped, Pressed: when the user is holding it down, Disabled: when the action is not available. Each of these states should be visually distinct, so the user understands what’s going on.\nCreating a structure for state-specific styles # We’ll define a RegularButtonStateStyle type that holds styling properties for each state: background, text color, font, size, and whether it’s outlined.\nimport SwiftUI public struct RegularButtonStateStyle { public typealias StateStyleConfiguration = (normal: Self, disabled: Self, pressed: Self) let size: RegularButtonSize let font: Font let backgroundColor: Color let foregroundColor: Color let isOutlined: Bool init( size: RegularButtonSize, font: Font, backgroundColor: Color, foregroundColor: Color, isOutlined: Bool = false ) { self.size = size self.font = font self.backgroundColor = backgroundColor self.foregroundColor = foregroundColor self.isOutlined = isOutlined } } StateStyleConfiguration is a convenient alias to bundle the three states into one logical unit.\nExample: primary, secondary, outline, text styles # Let’s define some commonly used visual styles:\npublic extension RegularButtonStateStyle { static func primary(size: RegularButtonSize) -\u0026gt; StateStyleConfiguration { let font: Font = size == .extraSmall ? .footnote : .subheadline return ( normal: .init(size: size, font: font, backgroundColor: .accentColor, foregroundColor: .white), disabled: .init(size: size, font: font, backgroundColor: .accentColor.opacity(0.5), foregroundColor: .gray), pressed: .init(size: size, font: font, backgroundColor: .accentColor.opacity(0.8), foregroundColor: .white) ) } static func secondary(size: RegularButtonSize) -\u0026gt; StateStyleConfiguration { let font: Font = size == .extraSmall ? .footnote : .subheadline return ( normal: .init(size: size, font: font, backgroundColor: Color(.systemGray6), foregroundColor: .primary), disabled: .init(size: size, font: font, backgroundColor: Color(.systemGray6), foregroundColor: Color(.systemGray2)), pressed: .init(size: size, font: font, backgroundColor: Color(.systemGray4), foregroundColor: .primary) ) } static func outline(size: RegularButtonSize) -\u0026gt; StateStyleConfiguration { let font = size == .extraSmall ? Font.footnote : Font.subheadline return ( normal: .init(size: size, font: font, backgroundColor: .clear, foregroundColor: .accentColor, isOutlined: true), disabled: .init(size: size, font: font, backgroundColor: .clear, foregroundColor: .accentColor.opacity(0.5), isOutlined: true), pressed: .init(size: size, font: font, backgroundColor: Color(UIColor.systemBackground), foregroundColor: .accentColor, isOutlined: true) ) } static func text(size: RegularButtonSize) -\u0026gt; StateStyleConfiguration { let font = size == .extraSmall ? Font.footnote : Font.subheadline return ( normal: .init(size: size, font: font, backgroundColor: .clear, foregroundColor: .blue), disabled: .init(size: size, font: font, backgroundColor: .clear, foregroundColor: .blue.opacity(0.5)), pressed: .init(size: size, font: font, backgroundColor: .clear, foregroundColor: .indigo) ) } } Applying state style configuration # Soon, inside RegularButtonStyle, we’ll be able to inject and use the full StateStyleConfiguration:\nButton(\u0026#34;Save\u0026#34;) {} .buttonStyle( RegularButtonStyle( stateStyleConfiguration: RegularButtonStateStyle.primary(size: .medium) ) ) 4. Supporting Different Sizes # Why does size matter? # Buttons come in different contexts:\nLarge CTA buttons on main screens, Compact buttons inside cards or toolbars, Micro buttons next to text. Having consistent size definitions makes the UI predictable and easier to scale.\nIntroducing RegularButtonSize # We’ll create an enum that defines fixed height and minimum width:\npublic enum RegularButtonSize { case extraSmall case small case medium case large var height: CGFloat { switch self { case .extraSmall: 38 case .small: 48 case .medium: 52 case .large: 56 } } var minWidth: CGFloat { switch self { case .extraSmall: 80 case .small, .medium, .large: 96 } } } This structure allows for consistent spacing and sizing across all button variations.\nHow it\u0026rsquo;s used in styles # Each RegularButtonStateStyle includes a size property, which can be accessed from within RegularButtonStyle:\n.frame(height: stateStyle(for: configuration).size.height) .frame( minWidth: stateStyle(for: configuration).size.minWidth, maxWidth: fillMaxWidth ? .infinity : nil ) This guarantees buttons will align consistently across views.\nSize also impacts font # We can adjust the font dynamically:\nlet font: Font = switch size { case .extraSmall: .footnote case .small, .medium, .large: .subheadline } This logic is already used inside RegularButtonStateStyle.\nAnd in RegularButtonStyle, we simply apply it:\n.font(stateStyle(for: configuration).font) 5. Button Shapes # Why offer different shapes? # Different shapes allow buttons to fit into different design contexts:\nCapsule: soft and friendly, commonly used for prominent CTAs. Rounded rectangle: more neutral, often consistent with cards and list items. Support for shape customization improves component flexibility and helps match your design system.\nEnum RegularButtonShape # Let’s define the shape options:\npublic enum RegularButtonShape { case capsule case rounded } Applying shape with a modifier # We’ll use a ViewModifier to apply the shape:\nprivate struct ShapeModifier: ViewModifier { let shape: RegularButtonShape let cornerRadius: CGFloat func body(content: Content) -\u0026gt; some View { switch shape { case .capsule: content.clipShape(Capsule()) case .rounded: content.clipShape(RoundedRectangle(cornerRadius: cornerRadius)) } } } In makeBody, we apply this modifier:\n.modifier(ShapeModifier(shape: shape, cornerRadius: Constants.cornerRadius)) Outline border support # For outline styles, we also want a stroke around the shape:\n.background { if stateStyle(for: configuration).isOutlined { switch shape { case .capsule: Capsule().stroke(foregroundColor(configuration), lineWidth: 1) case .rounded: RoundedRectangle(cornerRadius: Constants.cornerRadius) .stroke(foregroundColor(configuration), lineWidth: 1) } } } Making it configurable # We add the shape parameter to the style initializer:\npublic init( stateStyleConfiguration: RegularButtonStateStyle.StateStyleConfiguration, fillMaxWidth: Bool = false, shape: RegularButtonShape = .capsule, isLoading: Bool = false ) { self.stateStyleConfiguration = stateStyleConfiguration self.fillMaxWidth = fillMaxWidth self.shape = shape self.isLoading = isLoading } Usage example # Button(\u0026#34;Rounded Button\u0026#34;) {} .regularButtonStyle(shape: .rounded) 6. Handling isPressed and isEnabled # Why is this important? # Users expect feedback from UI elements:\nThe button should visually respond when pressed (isPressed) The button should appear inactive when disabled (isEnabled == false) Without visual feedback, the interface feels unresponsive and confusing.\nSwiftUI provides built-in support for this:\nconfiguration.isPressed indicates if the button is actively pressed @Environment(\\.isEnabled) indicates whether the button is enabled or not Selecting the correct state style # In RegularButtonStyle, we define a helper method to choose the appropriate style configuration:\nprivate func stateStyle(for configuration: Configuration) -\u0026gt; RegularButtonStateStyle { if configuration.isPressed { return stateStyleConfiguration.pressed } else if isEnabled { return stateStyleConfiguration.normal } else { return stateStyleConfiguration.disabled } } Order matters: isPressed takes priority over isEnabled.\nApplying the selected style # Now we use stateStyle(for:) to apply the correct values dynamically:\n.font(stateStyle(for: configuration).font) .foregroundStyle(foregroundColor(configuration)) .background(backgroundColor(configuration)) .frame(height: stateStyle(for: configuration).size.height) Each visual attribute adjusts based on the button’s state.\nExample usage # A button that visually reflects its disabled state:\nButton(\u0026#34;Save\u0026#34;) {} .regularButtonStyle( stateStyleConfiguration: RegularButtonStateStyle.primary(size: .medium) ) .disabled(true) 7. Supporting isLoading # Why support loading state? # Buttons often trigger asynchronous actions, like API calls, where it’s important to:\nPrevent multiple taps while waiting, Show visual feedback that the operation is in progress. A loading indicator provides a clear signal to the user that something is happening.\nAdding isLoading to the style # Extend the initializer of RegularButtonStyle:\npublic init( stateStyleConfiguration: RegularButtonStateStyle.StateStyleConfiguration, fillMaxWidth: Bool = false, shape: RegularButtonShape = .capsule, isLoading: Bool = false ) { self.stateStyleConfiguration = stateStyleConfiguration self.fillMaxWidth = fillMaxWidth self.shape = shape self.isLoading = isLoading } Replacing label with ProgressView # In the makeBody(...) method, swap out the button content with a spinner when loading:\nZStack { if isLoading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: foregroundColor(configuration))) } else { configuration.label } } Disabling the button while loading # .disabled(isLoading) This ensures the user can’t interact with the button multiple times.\nAnimating the transition # Add animation for a smoother UI experience:\n.animation(.bouncy, value: isLoading) If you\u0026rsquo;re targeting iOS 16 or earlier, replace .bouncy with .easeInOut\nExample usage # @State private var isLoading = false Button(\u0026#34;Submit\u0026#34;) { isLoading = true Task { try? await Task.sleep(nanoseconds: 1_000_000_000) isLoading = false } } .regularButtonStyle(isLoading: isLoading) 8. Creating a Convenient .regularButtonStyle(...)Modifier # Why use a custom modifier? # After adding multiple configuration options, the default .buttonStyle(...) call can become verbose:\nButton(\u0026#34;Click me\u0026#34;) { // ... } .buttonStyle( RegularButtonStyle( stateStyleConfiguration: RegularButtonStateStyle.primary(size: .medium), fillMaxWidth: true, shape: .capsule, isLoading: false ) ) To make this cleaner and more intuitive, we can wrap it in a custom view modifier.\nImplementing the extension # public extension SwiftUI.View { func regularButtonStyle( stateStyleConfiguration: RegularButtonStateStyle.StateStyleConfiguration = RegularButtonStateStyle.primary(size: .medium), fillMaxWidth: Bool = false, shape: RegularButtonShape = .capsule, isLoading: Bool = false ) -\u0026gt; some View { self.buttonStyle( RegularButtonStyle( stateStyleConfiguration: stateStyleConfiguration, fillMaxWidth: fillMaxWidth, shape: shape, isLoading: isLoading ) ) } } Thanks to default parameters, most buttons can be declared with minimal syntax.\nExample usages # Simple use with default parameters # Button(\u0026#34;Continue\u0026#34;) {} .regularButtonStyle() Secondary style with loading state # Button(\u0026#34;Save\u0026#34;) {} .regularButtonStyle( stateStyleConfiguration: RegularButtonStateStyle.secondary(size: .small), isLoading: true ) Full-width, rounded shape # Button(\u0026#34;Submit\u0026#34;) {} .regularButtonStyle( fillMaxWidth: true, shape: .rounded ) Benefits of this approach # Cleaner API for consumers More readable and expressive usage Aligns with SwiftUI’s declarative design principles 9. Preview and Testing # Why use previews? # SwiftUI’s live previews allow us to quickly test how components behave:\nView different configurations side-by-side Validate layout, responsiveness, and states Save time without needing to run the full app Creating a dedicated preview for our button helps us verify that all variations look and behave correctly.\nPreview container implementation # private struct RegularButtonPreviewContainer: View { @State private var isLoading: Bool = false var body: some View { VStack(spacing: 16) { Button(\u0026#34;Toggle Loading\u0026#34;) { isLoading.toggle() } .buttonStyle(.borderedProminent) Divider() Group { Button(\u0026#34;Primary\u0026#34;) {} .regularButtonStyle(isLoading: isLoading) Button(\u0026#34;Primary Rounded\u0026#34;) {} .regularButtonStyle(shape: .rounded, isLoading: isLoading) } Group { Button(\u0026#34;Secondary\u0026#34;) {} .regularButtonStyle( stateStyleConfiguration: RegularButtonStateStyle.secondary(size: .medium), isLoading: isLoading ) Button(\u0026#34;Secondary Rounded\u0026#34;) {} .regularButtonStyle( stateStyleConfiguration: RegularButtonStateStyle.secondary(size: .medium), shape: .rounded, isLoading: isLoading ) } Group { Button(\u0026#34;Outline\u0026#34;) {} .regularButtonStyle( stateStyleConfiguration: RegularButtonStateStyle.outline(size: .medium), isLoading: isLoading ) Button(\u0026#34;Outline Rounded\u0026#34;) {} .regularButtonStyle( stateStyleConfiguration: RegularButtonStateStyle.outline(size: .medium), shape: .rounded, isLoading: isLoading ) } Group { Button(\u0026#34;Text\u0026#34;) {} .regularButtonStyle( stateStyleConfiguration: RegularButtonStateStyle.text(size: .medium), isLoading: isLoading ) } } .padding() } } Launching the preview # #Preview { RegularButtonPreviewContainer() } This provides a live environment where you can toggle loading and observe UI behavior instantly.\n10. Final Thoughts and Scalability # What we’ve built # We’ve built a SwiftUI button component that:\nUses the ButtonStyle protocol for customization Supports: Multiple visual styles (primary, secondary, outline, text) Sizes and shapes Disabled, pressed, and loading states Exposes a clean and reusable .regularButtonStyle(...) modifier Includes a preview system for testing and demonstration Ideas for future expansion # Add support for icons and icon alignment Use design tokens for spacing, font, and color Integrate haptic feedback Improve accessibility with VoiceOver traits Add UI snapshot testing Bundle as a Swift Package for sharing When this is overkill # For simple apps with one or two buttons, this abstraction may feel heavy. In a larger project, the payoff is that button styling lives in one place, so a design change is a change in one file.\nRelated Links # GitHub link to repo ButtonStyle | Apple Developer Documentation ","date":"30 April 2025","externalUrl":null,"permalink":"/post/reusable-swiftui-button/","section":"Posts","summary":"The short guide on building a reusable SwiftUI button with styles, sizes, shapes and a loading state.\n","title":"Reusable SwiftUI Button","type":"post"},{"content":"The short guide on how to render custom view in swipe action.\nTask # Use SwiftUI views inside swipeActions by prerendering them into Image objects, overcoming UIKit limitations.\nSolution # In SwiftUI, swipeActions only accept Image/UIImage as button labels. This restricts the ability to use rich SwiftUI layouts like Circle, RoundedRectangle, or even combinations of views with SF Symbols and colors. To overcome this, we can render SwiftUI views into images.\nImageRenderer # ImageRenderer (iOS 16+) converts any SwiftUI view into a UIImage. That helps in places like swipeActions that only accept image-based content.\nHere’s how it works:\nYou wrap your SwiftUI content in a fixed-size container. You pass it to ImageRenderer. You retrieve a UIImage (or Image for SwiftUI) that visually matches your SwiftUI view. The rendered image can contain any SwiftUI content, such as shapes, text and SF Symbols, and can be used anywhere a UIImage is expected.\nImplementation # Here’s a reusable function that does the rendering:\nfunc render(size: CGSize, @ViewBuilder content: () -\u0026gt; some View) -\u0026gt; Image? { let renderer = ImageRenderer( content: content() .frame(width: size.width, height: size.height) ) renderer.scale = UIScreen.main.scale renderer.proposedSize = .init(size) return renderer.uiImage.map { Image(uiImage: $0) } } size defines the exact output size of the image. .proposedSize ensures the renderer respects layout. The SwiftUI view is framed and passed into the renderer. Benefits\nUse rich SwiftUI layout (shapes, overlays, icons) where only Image is allowed Retain visual consistency with the rest of your UI Customize light/dark mode rendering via SwiftUI environment if needed Combine this with caching for performance Notes # Works on iOS 16 and later . You can wrap this rendering in a cache layer for efficiency. .tint(.clear) is important to avoid system tint from affecting your rendered image. Demo Project # import SwiftUI struct ContentView: View { @Environment(\\.displayScale) var displayScale @State private var items = Array(repeating: \u0026#34;0\u0026#34;, count: 10) var body: some View { List { ForEach(items.indices, id: \\.self) { index in Text(\u0026#34;Row \\(index)\u0026#34;) .swipeActions(edge: .trailing) { Button { print(\u0026#34;Tapped action on row \\(index)\u0026#34;) } label: { render(size: CGSize(width: 36, height: 36)) { Circle() .fill(Color.blue) .overlay { Image(systemName: \u0026#34;star.fill\u0026#34;) .foregroundColor(.white) } } } .tint(.clear) Button { print(\u0026#34;Tapped action on row \\(index)\u0026#34;) } label: { render(size: CGSize(width: 36, height: 36)) { RoundedRectangle(cornerRadius: 6) .fill(Color.blue) .overlay { Image(systemName: \u0026#34;document.fill\u0026#34;) .foregroundColor(.white) } } } .tint(.clear) } } } } // MARK: - Renderer func render(size: CGSize, @ViewBuilder content: () -\u0026gt; some View) -\u0026gt; Image? { let renderer = ImageRenderer( content: content() .frame(width: size.width, height: size.height) ) renderer.scale = UIScreen.main.scale renderer.proposedSize = .init(size) return renderer.uiImage.map { Image(uiImage: $0) } } } Related # ImageRenderer | Apple Developer Documentation ","date":"1 February 2025","externalUrl":null,"permalink":"/post/render-swipe-actions/","section":"Posts","summary":"The short guide on how to render custom view in swipe action.\n","title":"Render Swipe Actions in SwiftUI","type":"post"},{"content":"The short guide on how to track the interface orientation in SwiftUI.\nTask # Track the interface orientation for the current view in order to implement layout correctly.\nSolution # Orientation tracking class:\npublic final class IsPortraitOrientationBroadcaster: ObservableObject { public static let shared = IsPortraitOrientationBroadcaster() @Published var isInterfacePortrait: Bool = true private var interfaceOrientation: UIInterfaceOrientation? { (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.interfaceOrientation } private init() { // Set initial orientation based on the interface orientation if let interfaceOrientation { isInterfacePortrait = interfaceOrientation.isPortrait } // Observe orientation changes based triggered by device orientation change NotificationCenter.default.addObserver( forName: UIDevice.orientationDidChangeNotification, object: nil, queue: .main ) { [weak self] _ in guard let interfaceOrientation = self?.interfaceOrientation else { return } self?.isInterfacePortrait = interfaceOrientation.isPortrait } } } Property wrapper for convenient use:\n@propertyWrapper public struct IsPortraitOrientation: DynamicProperty { @ObservedObject private var broadcaster: IsPortraitOrientationBroadcaster = .shared public init() {} public var wrappedValue: Bool { broadcaster.isInterfacePortrait } } How to Apply # You can now easily use this Property Wrapper in SwiftUI to manage the interface based on orientation:\nstruct ContentView: View { @IsPortraitOrientation var isPortrait var body: some View { Text(isPortrait ? \u0026#34;Portrait\u0026#34; : \u0026#34;Album\u0026#34;) } } What’s Happening Here? # Orientation # When tracking device orientation in iOS, it’s important to understand the difference between UIWindowScene.interfaceOrientation and UIDeviceOrientation.\nUIWindowScene.interfaceOrientation # What It Is: The interface orientation of the app, reflecting the current position of the interface relative to the device screen.\nHow to obtain it: UIApplication.shared.windows.first?.windowScene?.interfaceOrientation Note that there can be multiple scenes in an app, so the orientation must be used for the specific scene.\nWhen Available: Available in the context of the active window of the app (scene), rather than the entire device.\nPros\nAlways available right after the app starts, which is useful if you need to ensure the correct interface state from the very beginning Reflects the current state of the interface and is used to manage interface changes when the orientation switches (for example, switching between portrait and landscape mode) Remains stable if the interface orientation is locked, regardless of the physical orientation of the device Be aware\nMay not match the interface orientation if, for example, the interface is locked in portrait orientation but the device is in landscape orientation UIDeviceOrientation # What It Is: The physical orientation of the device, tracked by system hardware sensors.\nHow to obtain it: UIDevice.current.orientation\nWhen Available: Always available, but there are nuances regarding how quickly it updates.\nPros\nReflects the physical position of the device. Useful in cases where it’s important to account for the actual movements of the device, such as when working with the camera. Be aware\nAfter the app starts, it may return a value of .unknown, as the orientation only updates after a physical change in device position. Therefore, it’s not always reliable at app launch May not match the interface orientation if, for example, the interface is locked in portrait orientation but the device is in landscape orientation Important Note # There are examples on the internet of mapping UIDeviceOrientation to UIInterfaceOrientation and vice versa. This approach can lead to inconsistencies, especially if the interface is locked or if data about the physical orientation of the device has not yet updated.\nProperty Wrapper # Using a Property Wrapper is not mandatory but simplifies the process, making the code more concise and convenient. In this example, @IsPortraitOrientation automatically tracks orientation changes, saving you from manually updating the state.\nRelated Links # UIInterfaceOrientation | Apple Developer Documentation UIDeviceOrientation | Apple Developer Documentation ","date":"13 October 2024","externalUrl":null,"permalink":"/post/tracking-interface-orientation/","section":"Posts","summary":"The short guide on how to track the interface orientation in SwiftUI.\n","title":"Tracking Interface Orientation in SwiftUI","type":"post"},{"content":"I\u0026rsquo;m a senior iOS engineer with 8+ years of commercial experience, based in the USA.\nWhat I work on # Most of my career has gone into the parts of mobile apps that are hard to get right: real-time calling (CallKit, AVAudioSession, LiveKit), message sync and local storage, and large legacy codebases built on Core Data. I built and maintained a calling stack from scratch on my own, which taught me more about system behavior than any documentation did. These days I also explore SwiftData in side projects.\nWhat I\u0026rsquo;ve built # Picklez is a party game for iPhone and iPad that I designed, developed and published on my own: an Alias-style word-guessing game combined with \u0026ldquo;would you rather\u0026rdquo; dilemmas across 24 content topics. It\u0026rsquo;s built in SwiftUI, with unlockable content packs sold through StoreKit 2 (async/await for transaction handling and entitlement verification). I owned the whole lifecycle: architecture, UI/UX, App Store submission and ASO. The app collects no user data by design. Free on the App Store, in English and Russian.\nHow I think about it # I\u0026rsquo;d rather understand why something works than get it to \u0026ldquo;kinda work\u0026rdquo;. That\u0026rsquo;s the whole idea of this blog: notes from digging into the details, written down so the next person (often me, six months later) doesn\u0026rsquo;t have to dig again.\nOutside of code # Homelab tinkering, reading quarterly reports as an investor, and photography.\nElsewhere # Career details and the full history are on LinkedIn. Code is on GitHub.\n","externalUrl":null,"permalink":"/about/","section":"Swifty","summary":"","title":"About Igor Skvortsov","type":"page"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"Notes on Swift, SwiftUI, UIKit and the odd system behavior the docs skip. Some posts are guides, some are me thinking out loud. The goal is always the same: understand it properly, not just make it compile.\nMore about me on the About page.\n","externalUrl":null,"permalink":"/","section":"Swifty","summary":"","title":"Swifty","type":"page"}]