Shadowing Practice: My 4.9-Star iOS App Is Actually a Website: Here’s How I Built It - Learn English Speaking with Video

Choose a practice mode

Each mode opens a full-screen practice room.

239 sentences
If sentences are too short or too long, click Edit to adjust them.
1
In the last video, I presented web apps as an alternative to native mobile development.
2
The single biggest objection in the comments was about Electron.
3
The assumption that web apps are fundamentally broken and always result in enormous bundle sizes and poor user experience.
4
Well, I dislike Electron apps as much as you do, to the point where I can tolerate only a couple of them on my Mac.
5
And the smallest one is nearly 500 megabytes in size.
6
But here's my web app.
7
It's under 10 megabytes and rated 4.9 on the App Store.
8
In this video, I'll explain the difference.
9
More importantly, I'll show you how to build web apps
10
that offer a user experience comparable to Swift UIs and a developer experience that's far better than Swift UIs.
11
Oh, and you can deploy such an app in three minutes and without getting an approval from Apple or anyone else.
12
Does that sound too good to be true?
13
You're right to question this.
14
Replacing an Electron with WebKit wouldn't magically make your app better.
15
The web stack is full of hidden friction points that make unoptimized web apps feel wrong to mobile users.
16
So when you want a product that has the look and feel of a native app, first you have to make sure that the web app itself is fast, powerful and responsive.
17
And that's what we'll focus on today.
18
No native shells, no pin into the home screen, only a plain website.
19
But one that runs better than half of all SwiftUI apps and better than 99% of all wipe-coded garbage.
20
This video is not a coding tutorial, because you don't need my help to write a JavaScript function.
21
Instead, I'm offering you a deep dive into my apps architecture.
22
I break down its key aspects, explain my design decisions, and offer architectural advice you can apply to your next or your first web project.
23
And if you've got channel membership, today's extras include expanded explanations, more examples, and a detailed breakdown of my deployment process.
24
If you don't have a membership, no worries.
25
The public version of this video still includes everything important for getting started.
26
In case you missed the last video in which I explained the app's concept, Space Photos is my playground project.
27
It's a simple app that presents images of the universe fetched from NASA's astronomy Picture of the Day service.
28
It is super fast, works on screens of any size, and lets you build a collection of your favorite pictures.
29
In terms of software, Space Photos is built as a progressive web app.
30
It works in the browser as a plain website, on the home screen as a pinned app, and in a lightweight shell as a native iOS app.
31
At each level, it gains more native-like features.
32
The web app is completely self-sufficient and keeps everything in a single repo.
33
That everything includes the actual metadata for each space photo, its title, explanation text and media URLs.
34
In a larger or more complex app, I would probably keep production data in a separate place, perhaps a dedicated database.
35
But in this case, storing the JSON files in the same repo enabled faster builds and some useful build time optimizations,
36
while taking just 13 MB for over 30 years of space photos.
37
At this point, it seems that I can no longer avoid answering the question I got asked multiple times in the comments.
38
What specific frontend framework space photos is built with?
39
I didn't name it in the last video, because doing so requires providing enough context.
40
Now's the time.
41
I always say that any technology is just a tool, and you should pick the best one for your specific job.
42
I even put this in the channel description.
43
So let's first consider the functional and non-functional requirements of space photos.
44
First, if I want even the slightest chance to compete with native apps, the framework must incur as little overhead as possible while playing nicely with all the modern web standards.
45
Second, because this was actually my first serious web app, I didn't want to commit to any single framework, just in case I'll need to swap it for something else later.
46
That is why standard HTML syntax was preferred over any specialized tools like JSX.
47
Third, I enjoy clear separation of concerns, where business logic is or can't be entirely decoupled from the user interface.
48
Only when I had this list of requirements did I actually start looking at my options.
49
And while more than one framework matches this criteria, for this specific project I chose SvelteKit.
50
Here's why.
51
First of all, SvelteKit is not really a framework, but more like a compiler.
52
SvelteKit compiles down to vanilla JavaScript and has no virtual DOM.
53
At runtime, it is nearly as fast as bare handwritten code developed 30 years ago.
54
The bundle of a fully functional app is roughly 50 kilobytes in size, or about half as much when it's GZ.
55
SvelteKit's syntax is based on standard HTML.
56
You don't have to learn some new JSX or XML-like language, so if you decide to switch to a different framework in the future, you can reuse most of your code.
57
SvelteKit does not enforce any particular design pattern, but it fits the MVC and MVVM patterns quite naturally.
58
My personal preference is to do as much as possible in the server components, which are roughly equivalent to the controller in MVC.
59
Although in my app they're not really server components, but rather build time components.
60
My app is fully pre-rendered, which is another advantage.
61
The data flow is reactive, which is something I was cautious about at first, because of my poor experience with SwiftUI.
62
But in reality, SvelteKit's reactivity is much more fine-grained.
63
It doesn't update entire screens whenever something changes.
64
Instead, it's based on individual signals you're subscribed to, similar to Cocoa bindings.
65
And finally, I find SvelteKit's out-of-the-box experience very dev-friendly.
66
It ships with full support for routing, server-side rendering, static-side generation, and many other things.
67
If you only need the reactivity and reusable UI components, you can use Svelte alone, without the kit.
68
But so far, I've been quite content with the features and developer experience of the entire framework.
69
One aspect of that developer experience I should highlight specifically is the support for hot reloading.
70
This isn't something unique to SvelteKit, this feature exists in most front-end frameworks.
71
But I just can't stop appreciating how stable and reliable it is.
72
Updated pages refresh instantly and even keep any user input intact.
73
But if there's a syntax error, you'll get a warning immediately.
74
Try to achieve the same in SwiftUI.
75
By the way, maybe you heard that story where Apple accidentally leaked the sources of the App Store's web version.
76
Guess what front-end framework is built with?
77
That's right.
78
So, moving on from the developer experience to the actual development process, let's have a look at the core of SpacePhotos.
79
First, the overall approach to the app's layout.
80
When you want a good mobile app, you should build a mobile version first.
81
A single viewport directive disables the standard zoom out behavior, sets the scale to 100% and lets you use the screen parts outside the safe area.
82
The latter will be important when you run the app in the standalone mode with no browser toolbars.
83
Because I wanted to minimize the friction for my app's existing users, I decided to sort of imitate the familiar look of a typical iOS app.
84
This means my app should have a tab bar on the bottom and a and a navigation bar on the top.
85
Both are simple panels that sit above the main document and are pinned in place using the fixed position mode.
86
Importantly, because I made the panels appear semi-transparent and have a blur, the page content must scroll beneath them.
87
Both goals are easily achieved with only a few lines of CSS.
88
You see, HTML documents can be infinitely long, which turns the browser canvas into a giant scroll view.
89
When you reach the end of it, the entire document, including the bars, would exhibit rubber bending and can also trigger a page refresh.
90
This is what immediately gives away the true nature of the app.
91
In native ones, the bars always remain stationary, while the content can scroll, stretch, and bounce back.
92
And that is exactly how it works in my web app.
93
Things like this really do matter for the overall user experience.
94
And actually, they're not impossible to recreate.
95
You just have to care enough.
96
For example, I once had to subclass the UI components of Firebase in AppMessaging and extend them with native-like interactions.
97
I used UIKit Dynamics to add physics-based rubber bending and swipe to dismiss.
98
It took me quite a while to figure out the precise formula, but the resulting animations felt very familiar to iOS users.
99
They didn't have those uncanny valley vibes you often get with cross-platform frameworks.
100
By that point, space photos looked just right on mobile.
101
Bringing it to larger screens required only a couple of adjustments.
102
First, I limited the container's maximum width to 800 points.
103
I did it so the UI doesn't feel absurdly wild on large monitors.
104
This takes exactly one line of CSS.
105
And second, I configured the tab bar to move from the bottom to the top when the width exceeds a certain threshold.
106
This required only a couple of lines too.
107
The next point is the look and feel.
108
As I mentioned in my previous video, every UI element of space photos works equally well with all input methods.
109
Touch, mouse and trackpad.
110
In most cases, I didn't even have to do anything special because nearly every HTML element adapts correctly out of the box.
111
But there's a cache.
112
You have to use those elements according to their semantic rules.
113
For instance, the button element on the web is not really designed to take the user elsewhere.
114
Its purpose is to perform some local ephemeral action, open a menu, expand a section, close a model sheet, and so on.
115
And while it's totally possible to trigger navigation using buttons, this negatively impacts accessibility, which is quite important.
116
A more conventional approach is to use the standard anchor element, also known as the link.
117
You can style it to look just like a button, with background, padding, and whatever else.
118
Doing so automatically enables the behaviors you associate with links, the hover in active states, the pointer cursor, and the ability to open them in a new tab.
119
The button can't do this without a lot of extra code.
120
This is why most elements that look like buttons in my app are actually links.
121
This includes the tabs in the tab bar, space photo cards and thumbnails, and cells in the favorites list.
122
There are still places where I used plain buttons, mostly because I needed to trigger haptic feedback, but I'm working on turning them back into links.
123
Another aspect is navigation.
124
A typical single-page app or SPA is just that, a single page.
125
This offers the exact experience many of you associate with web apps, and I admit, it's not a good one.
126
First the SPA shows an empty template.
127
Then it displays a loading indicator and starts patching the data, which takes 3, 5, 10 seconds or even longer.
128
it shows the content, often after a flash of white or a jump in a layout.
129
At that point, you can finally see the content and interact with it.
130
You can even navigate to other screens, or so it appears.
131
To the browser, all this is just an opaque box that's doing something.
132
Every screen of the app has the same URL, so you can't easily go back or share a link with a friend.
133
And web search engines really hate this setup.
134
It didn't work for me either.
135
The whole point of taking space photos to the web was to make each photo easily discoverable and easily shareable.
136
That is why I implemented navigation the same way it worked 30 years ago.
137
But thanks to this support for client-side navigation in SvelKit and transitions in CSS, navigation looks much better than it looked 30 years ago.
138
Technically, you can even achieve the push and pop animations from view controllers in iOS.
139
However, after some testing, I decided to use a simple transition without the entire page sliding over another one.
140
This didn't look good on large desktop screens.
141
As I was working on this video, out of curiosity, I checked some of those Mac apps ported over from the iPad.
142
Turns out Apple has removed push and pop from most of them too.
143
Apple News is a notable exception.
144
First, it slides a new view controller over the old one.
145
And then it sends me to the browser anyway.
146
That's true genius of user-friendly UI, I'd say.
147
Again, navigation and space photos does not use some deep links resolved dynamically.
148
These are 100% real links.
149
When you click on one, it shows the content immediately with no extra loading, as if the space photo was already baked into the physical HTML document.
150
That's because it is.
151
Despite client-side navigation being available, the entire app is still pre-rendered at build time as a collection of standalone HTML documents.
152
That means users can start browsing space photos on any page.
153
And when they wanna go elsewhere, client-side navigation takes over and shows new content almost immediately and without a full reload.
154
Implementing this presented one completely unexpected problem.
155
I'll detail it in the bonus section about my deployment setup.
156
But if you don't want to subscribe to channel membership, don't worry.
157
This problem is unlikely to affect you, at least as long as your product doesn't have tens of thousands of pages.
158
Next are a couple of native features.
159
While I'll be breaking down the native shell in a future episode, there's still a lot of features that can make a web app feel more native even without a real iOS wrapper.
160
This time I'll cover those that work right in the browser, even when the app is not pinned to the home screen.
161
The most obvious one is support for the light and dark modes.
162
In CSS, enabling it takes setting a single rule.
163
It works in every browser.
164
Then you have to customize your app's color palette.
165
If you use semantic naming, just like it works in iOS, then overriding color values for the dark mode is a straightforward process.
166
The ability to store data persistently on device is what makes my app stand out, even compared to many native alternatives.
167
Back when it was a UI key app, I used Core Data for this purpose, but the web offers a robust alternative.
168
In the end, I was able to migrate from Core Data to IndexedDB with no loss of functionality and most importantly, no loss of data.
169
In a future episode, I'll explain how I moved existing records without users noticing anything.
170
To keep the length of this video reasonable, I skipped sections about accessibility and cross-browser testing.
171
I plan to cover them in separate episodes, so if you're interested, subscribe to the channel.
172
For now, let's assume that your web app works as expected on every major rendering engine.
173
So it's finally time to deploy it.
174
To keep space photos easily maintainable, I wanted to limit the number of external dependencies.
175
That is why, instead of building a live API backend, I decided to deploy the app statically.
176
As I already mentioned, SvelteKit supports pre-rendering and static site generation.
177
Building my 11,000-page app takes about 20 seconds on a laptop.
178
But obviously, production deployments use continuous integration.
179
Overall, it takes GitHub actions about 3 minutes to check out the repo, install the dependencies, build a project,
180
package it for distribution, and deploy it to Cloudflare pages.
181
Hosting a static site at the edge results in unmatched loading speed.
182
And with a couple optimization tricks explained in the following bonus section, many navigation actions don't even make extra network calls.
183
Did you notice one thing that's absent from this deployment process?
184
It's the App Store submission.
185
While Apple has greatly reduced the average review time for iOS apps in the recent years, it doesn't come close to my three-minute deployment.
186
There will be one important section right after this one, but at this point I believe I've covered all the important aspects
187
that provide a foundation for web apps that look and feel just like native apps, if not better.
188
As for the developer experience, I think you'll agree that it's much better than the Swift UIs already.
189
In the next episode, I'll cover the things that turn a regular website into a true progressive web app.
190
If you've got any questions or advice, I always welcome them in the comments.
191
Just one request, please keep your temper.
192
Claiming that WebKit on iOS is bad just because you don't like Electron apps on your PC sounds kind of ridiculous.
193
That's not what I would expect from senior engineers.
194
Speaking of electron.
195
Just before we wrap up, I will address one giant misconception I didn't pay enough attention to in my concept overview of PWA.
196
It sounds like this.
197
You're selling me an electron app.
198
I hate electron apps.
199
Everybody hates electron apps.
200
Electron apps.
201
Ah!
202
You don't like electron apps, and rightly so, but you blame the wrong part.
203
Electron's problem is not the web app inside, it's what surrounds it.
204
Every Electron app comes with a full copy of Chromium and all its dependencies, plus a large collection of native bindings to handle every use case on every platform.
205
This results in enormous bundle sizes for simple apps like chat clients.
206
Installing a new app literally adds another Chromium instance on your computer.
207
In contrast, WebKit is a system framework.
208
Instead of copying it, apps link against WebKit dynamically.
209
As for those native bindings, you only implement what you will be using.
210
There are frameworks such as Cordova and Capacitor, but I decided not to use them in space photos, precisely to keep its size under control.
211
These two differences dramatically reduce the bundle size.
212
For instance, my app is under 10 MB, most of which are actually static resources like the longscreen image.
213
At runtime, each Electron app start its own separate Chromium instance complete with all the helper processes.
214
Inside it runs your actual web app.
215
If you have two electron apps running side by side, they use roughly twice the memory.
216
This is like launching a separate web browser for every single website you visit.
217
Now see what happens when you run multiple WebKit apps.
218
Because it is a system framework, its rendering and JavaScript engines can be reused across processes, and they are.
219
The client code, including the DOM tree, the JS heap, and cookies is still sandbox to isolate apps from one another.
220
Conceptually, this is just like having a browser with one tab and then opening a second one.
221
Does this result in the doubling of the resource footprint?
222
No, it doesn't.
223
Because WebKit itself is very lean, the product's overall speed depends squarely on the web app that you write.
224
If your code is inefficient, the app will feel slow.
225
But you know who to blame in this case.
226
So the question comes down to whether you're comfortable taking responsibility for your own code.
227
I am, and that is why I'm not intimidated by WebKit, Cocoa, AutoLayout, and even Interface Builder.
228
I know that when you use them correctly, they work exactly the way you want them to and produce rock-solid, blazing-fast results, unlike SwiftUI's Blackbox.
229
So it's only a matter of engineering culture, I guess.
230
That's it for today's Codebird video.
231
If you found it illuminating, give it a thumbs up.
232
As much as I wanted to keep this episode relatively short, the director's cut still adds up to over 4000 words.
233
But I believe this is better than bringing you a high-dynamics video with terribly limited perspective.
234
You can't build good products if you operate on simple maxims like native good, web bad.
235
Sorry, but that's just immature.
236
In the next episode, we'll take another step in building web apps that feel like native apps.
237
If you're interested, subscribe to the code board and stay tuned.
238
I'll see you in the next video.
239
Until then, make your web apps fly.

Download App

Everything you need to speak fluently

AI PronunciationScore every sentence
IPA PracticeMaster every sound
VocabularyBuild your word bank
Vocab GameLearn while playing
FOR YOU

Recommend Video

Vocabulary and speaking notes for this lesson

This video has 239 sentences and 3438 words to shadow. The speech runs for 19:07. The speaker talks fast, about 180 words per minute, so expect linked and reduced sounds. Only 78% of the words are among the 3,000 most common in English, so the vocabulary is demanding.

Key vocabulary in this video

15 less common words from the video, with pronunciation and meaning:

  • electron /ɪˈlɛk.tɹɑn/ (noun) — The subatomic particle having a negative charge and orbiting the nucleus; the flow of electrons in a conductor constitutes electricity.
  • framework /ˈfɹeɪm.wɜːk/ (noun) — A support structure comprising joined parts or conglomerated particles and intervening open spaces of similar or larger size.
  • navigation /nævɪˈɡeɪʃən/ (noun) — The theory, practice and technology of charting a course for a road vehicle, ship, aircraft, or spaceship.
  • browser /ˈbɹaʊ.zəɹ/ (noun) — One who or which browses.
  • render /ˈɹɛn.dɚ/ (verb) — To cause to become.
  • component /kəmˈpoʊ.nənt/ (noun) — A smaller, self-contained part of a larger entity. Often refers to a manufactured object that is part of a larger device.
  • developer /dɪˈvɛləpɚ/ (noun) — A person or entity engaged in the creation or improvement of certain classes of products.
  • element /ˈɛləmənt/ (noun) — One of the simplest or essential parts or principles of which anything consists, or upon which the constitution or fundamental powers of anything are based.
  • static /ˈstæt.ɪk/ (adjective) — Unchanging; that cannot or does not change.
  • bundle /ˈbʌn.dl̩/ (noun) — A group of objects held together by wrapping or tying.
  • deployment /dɪˈplɔɪmənt/ (noun) — An arrangement or classification of things.
  • deploy /dɪˈplɔɪ/ (verb) — To prepare and arrange (originally military unit or units, i.e., to array troops) for use.
  • membership (noun) — The state of being a member of a group or organization.
  • server /ˈsɝvɚ/ (noun) — A program that provides services to other programs or devices, either in the same computer or over a computer network.
  • plain /pleɪn/ (noun) — An expanse of land with relatively low relief and few trees, especially a grassy expanse.

Phrasal verbs you will hear

  • work in (verb) — To find time or space for, amid other things.
  • work on (verb) — To apply mental or physical effort to (something) in order to shape, form, improve, investigate, solve it, etc.
  • break down (verb) — To stop functioning.
  • look at (verb) — To observe or watch.
  • add up to (verb) — To have a particular effect in combination.
  • back into (verb) — To advance to the post-season as a result of another team's loss, especially where one's own team has also lost.
  • check out (verb) — To record the departure or withdrawal of someone or something (such as guests, employees, books, etc.).
  • come down to (verb) — To reach by moving down or reducing.

Pronunciation to watch

The speaker uses 57 contractions and reduced forms, such as I'll, don't, didn't. Say them the short way, as you hear them.

  • The “th” sounds: width /ˈwɪtθ/, beneath /bɪˈniːθ/, threshold /ˈθɹɛʃ(h)oʊld/
  • The “sh” and “zh” sounds: navigation /nævɪˈɡeɪʃən/, shell /ʃɛl/, associate /əˈsoʊʃi.ət/, friction /ˈfɹɪkʃən/, refresh /ɹɪˈfɹɛʃ/
  • Long words — get the stress right: navigation /nævɪˈɡeɪʃən/, developer /dɪˈvɛləpɚ/, importantly /ɪmˈpɔɹ.tənt.li/, associate /əˈsoʊʃi.ət/, accessibility /əkˌsɛs.əˈbɪl.ə.ti/

How to practise with this video

  1. Listen to the whole video once without speaking and note the words you do not know.
  2. Start at 0.75× speed, shadow it sentence by sentence, then go back to normal speed once it feels easy.
  3. Record yourself and compare with the original, paying attention to words like electron, framework, navigation.

Grammar in this video

The structures the speaker uses most, with the exact words from the video:

StructureIn the video
Conditional sentences if + clause, will/would + verb — a condition and its resultif there's a syntax error, you'll get
Passive voice be + past participle — the focus is on what happens, not who does itis built · was preferred · is based
Relative clauses who / which + clause — extra information about a person or thingcomponents, which are · rendered, which is · reactive, which is
Present perfect have/has + past participle — a past action that still matters nowI've been · has removed · I've covered

What is the Shadowing Technique?

Shadowing is a science-backed language learning technique originally developed for professional interpreter training and popularized by polyglot Dr. Alexander Arguelles. The method is simple but powerful: you listen to native English audio and immediately repeat it out loud — like a shadow following the speaker with just a 1–2 second delay. Unlike passive listening or grammar drills, shadowing forces your brain and mouth muscles to simultaneously process and reproduce real speech patterns. Research shows it significantly improves pronunciation accuracy, intonation, rhythm, connected speech, listening comprehension, and speaking fluency — making it one of the most effective methods for IELTS Speaking preparation and real-world English communication.

Shadowing technique: read the full step-by-step guide →