-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathmy-experience-with-swiftui.html
More file actions
351 lines (307 loc) · 26.3 KB
/
Copy pathmy-experience-with-swiftui.html
File metadata and controls
351 lines (307 loc) · 26.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://use.fontawesome.com/afd448ce82.js"></script>
<!-- Meta Tag -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- SEO -->
<meta name="author" content="Bruno Rocha">
<meta name="keywords" content="Software, Engineering, Blog, Posts, iOS, Xcode, Swift, Articles, Tutorials, OBJ-C, Objective-C, Apple">
<meta name="description" content="I had played with SwiftUI before, but it wasn't until now that I had built a complete app with it. Here's my experience!">
<meta name="title" content="Thoughts on SwiftUI vs UIKit">
<meta name="url" content="https://swiftrocks.com/my-experience-with-swiftui">
<meta name="image" content="https://swiftrocks.com/images/thumbs/thumb_dark.jpg">
<meta name="copyright" content="Bruno Rocha">
<meta name="robots" content="index,follow">
<meta property="og:title" content="Thoughts on SwiftUI vs UIKit"/>
<meta property="og:image" content="https://swiftrocks.com/images/thumbs/thumb_dark.jpg"/>
<meta property="og:description" content="I had played with SwiftUI before, but it wasn't until now that I had built a complete app with it. Here's my experience!"/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://swiftrocks.com/my-experience-with-swiftui"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="https://swiftrocks.com/images/thumbs/thumb_dark.jpg"/>
<meta name="twitter:image:alt" content="Page Thumbnail"/>
<meta name="twitter:title" content="Thoughts on SwiftUI vs UIKit"/>
<meta name="twitter:description" content="I had played with SwiftUI before, but it wasn't until now that I had built a complete app with it. Here's my experience!"/>
<meta name="twitter:site" content="@rockbruno_"/>
<meta name="fediverse:creator" content="@rockbruno@hachyderm.io">
<!-- Favicon -->
<!-- 16x16 -->
<link rel="shortcut icon" href="images/favicon/favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="images/favicon/favicon_180.png" sizes="180x180">
<link rel="icon" href="images/favicon/favicon_32.png" sizes="32x32" type="image/png">
<link rel="icon" href="images/favicon/favicon_48.png" sizes="48x48" type="image/png">
<link rel="icon" href="images/favicon/favicon_96.png" sizes="96x96" type="image/png">
<link rel="icon" href="images/favicon/favicon_144.png" sizes="144x144" type="image/png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap" rel="stylesheet">
<link rel="canonical" href="https://swiftrocks.com/my-experience-with-swiftui"/>
<!-- Bootstrap CSS Plugins -->
<link rel="stylesheet" type="text/css" href="css/bootstrap.css">
<!-- Prism CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/prism5.css">
<!-- Main CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/style49.css">
<link rel="stylesheet" type="text/css" href="css/sponsor5.css">
<!-- HTML5 shiv and Respond.js support IE8 or Older for HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://swiftrocks.com/my-experience-with-swiftui"
},
"image": [
"https://swiftrocks.com/images/thumbs/thumb_dark.jpg"
],
"datePublished": "2022-10-20T14:00:00+02:00",
"dateModified": "2023-01-12T10:00:00+02:00",
"author": {
"@type": "Person",
"name": "Bruno Rocha"
},
"publisher": {
"@type": "Organization",
"name": "SwiftRocks",
"logo": {
"@type": "ImageObject",
"url": "https://swiftrocks.com/images/thumbs/thumb_dark.jpg"
}
},
"headline": "Thoughts on SwiftUI vs UIKit",
"abstract": "I had played with SwiftUI before, but it wasn't until now that I had built a complete app with it. Here's my experience!"
}
</script>
</head>
<body>
<div id="main">
<!-- Blog Header -->
<!-- Blog Post (Right Sidebar) Start -->
<div class="container">
<div class="col-xs-12">
<div class="page-body">
<div class="row">
<div><a class="logo-link" href="https://swiftrocks.com">
<img id="logo" class="logo" alt="SwiftRocks" src="images/bg/logo2dark.png">
</a>
<div class="menu-large">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-large">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
<div class="menu-small">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-1">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-2">
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
</div>
<div class="content-page" id="WRITEIT_DYNAMIC_CONTENT">
<!--WRITEIT_POST_NAME=Thoughts on SwiftUI vs UIKit-->
<!--Add here the additional properties that you want each page to possess.-->
<!--These properties can be used to change content in the template page or in the page itself as shown here.-->
<!--Properties must start with 'WRITEIT_POST'.-->
<!--Writeit provides and injects WRITEIT_POST_NAME and WRITEIT_POST_HTML_NAME by default.-->
<!--WRITEIT_POST_SHORT_DESCRIPTION=I had played with SwiftUI before, but it wasn't until now that I had built a complete app with it. Here's my experience!-->
<!--DateFormat example: 2022-10-20T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE_LAST_MOD=2023-01-12T10:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE=2022-10-20T14:00:00+02:00-->
<title>Thoughts on SwiftUI vs UIKit</title>
<div class="blog-post">
<div class="post-title-index">
<h1>Thoughts on SwiftUI vs UIKit</h1>
</div>
<div class="post-info">
<div class="post-info-text">Published on 20 Oct 2022</div>
</div>
<p>I had played with SwiftUI sporadically in the past, but it wasn't until now that I had built a complete App Store-ready app with it. When SwiftUI was first announced I was very concerned about certain quirks of the framework and how it was probably not suitable for professional iOS development, but with the framework evolving constantly over the past couple of years it became time to re-evaluate that statement. Through <a href="https://burnoutbuddy.io">Burnout Buddy,</a> I had the chance to develop a production app 100% with SwiftUI. Here are my thoughts on what works and what could be improved, and how I would fit SwiftUI and UIKit in my day-to-day work.</p>
<h2>Things that are great</h2>
<h3>Previews / Hot Reloading</h3>
<p>I can't stress hard enough how awesome SwiftUI's previews are. The amount of time UIKit devs were wasting compiling and running code to make small iterative UI changes is unfathomable -- yes, there were LLDB tricks and tools like <i>Reveal</i> which could sort of let you make certain types of changes on the fly, but I don't know anyone who was actually able to flawlessly integrate these into their day-to-day work. The previews allow you to iterate on visual changes and sometimes even the app's logic itself extremely quickly, and its ability to bootstrap itself out of any arbitrary <code>View</code> pairs perfectly when building composable components.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>To be fair, the previews aren't perfect. I had a frequent bug where the preview refused to show up and was unable to do any form of debugging on the preview itself, and a lot of people reached out to say that the previews are painfully slow on their older Macbook models, but it generally works well and I'm very happy we finally have official hot-reloading support in iOS. I'm eager to see how it will be improved in the future.</p>
<h3>Development speed</h3>
<p>Another extremely notable SwiftUI benefit is how easy it is to build apps with it. You can quite literally build an app to start to finish in just a couple of minutes, something which would be unheard of in any professional-level UIKit app (meaning no Storyboards and similar features that are known for not being scalable).</p>
<p>While Burnout Buddy took me a couple of weeks to build, the majority of that time was spent on the logic and architecture. The UI itself for the entire app was built in just a couple of minutes.</p>
<p>It's important to mention that this is not always true. As we'll see down below there's an entire category of "more complicated" products where SwiftUI is actually <i>extremely detrimental</i> to development speed, but we should still appreciate that SwiftUI works amazingly well for the more straight-forward category of projects that the majority of developers deal with. It's also not too surprising that this is the case considering that they have been Apple's primary target audience for Xcode features since the dawn of iOS.</p>
<h3>It encourages great coding practices</h3>
<p>It's very easy to make a UIKit app that has everything shoved into one god object. You could theoretically do the same in SwiftUI, but you'd have a much harder doing so. The way views are setup and referenced between each other encourages you to break up your views into small independent pieces, which is an amazing habit to have when building apps of any size as it improves the quality of your project in multiple areas such as composability, scalability, testability and code readability. For developers who are not yet familiar with those concepts, SwiftUI is a great tool to learn and apply those practices.</p>
<h2>Things that are not so great</h2>
<h3>Great for simple things, not so great for not-so-simple things</h3>
<p>It's generally easy to build state logic with UIKit. Because there are no constraints on what things should look like, you can build everything on the fly and achieve virtually anything you want at the cost of having to write a lot of annoying UI code.</p>
<p>SwiftUI is the opposite on steroids. It's <b>very, very easy</b> to build UI with, and <b>very, very hard</b> to build any piece of real business logic that you wouldn't see in a simple Hello World tutorial.</p>
<p>My first thought when SwiftUI was announced was that there's <i>no way</i> it works seamlessly for complex apps, and I imagine that anyone who ever had experience with React on the web front had the same thought. The reason for that is because data-drivenness, SwiftUI's main concept that some people already were experienced with due to React functioning roughly in the same way, doesn't scale very well. SwiftUI forces you to shape your model in a way so that the complete structure and state of all views are static and known well in advance, which while not an impossible task, can be insanely difficult for views that have lots of moving parts and conditions.</p>
<p>The best example I can show of how data-drivenness can massively spike the complexity of an app is SwiftUI's <code>ForEach</code> API:</p>
<pre><code>ForEach(0..<7) { i in
MyObject(myStrings[i])
}</code></pre>
<p>If all you're doing is iterating a static list of elements, everything will work like in UIKit. You can build a loop that takes a fixed integer range and access the indexes without any additional trouble.</p>
<p>Iterating the <b>mutable state</b> of your view is a bit trickier, but still doable. While in UIKit this doesn't incur any extra architectural cost on your behalf, SwiftUI forbids you from doing so unless the content conforms to <code>Identifiable</code>. This can be quite annoying given that almost nothing in Swift's Standard Library conforms to this protocol, but the requirement makes sense in the context of SwiftUI where the framework relies on the concept of model identity and uniqueness in order to determine when the UI should be re-drawn.</p>
<pre><code>ForEach(myStrings) { string in
MyObject(string)
} // Error: String does not conform to Identifiable
// Solution: Replace "myStrings" with:
struct MyModel: Identifiable {
let id = ... // How this is implemented depends
// on what the model represents in practice and how it should be updated
let string: String
}</code></pre>
<p>Try to do anything more complicated than that though and you'll face the wrath of data-drivenness. Consider the simple use-case of keeping track of the position of an item in a list while iterating it, something which is very common in iOS to implement features like alternating background colors in views: While this is trivial to pull off in UIKit, attempting to introduce dynamic behavior like this in SwiftUI can result in your view's entire architecture being body slammed!</p>
<pre><code>ForEach(contentList.enumerated()) { info in
MyObject(info.element, info.offset)
} // Error: Arbitrary sequences cannot be iterated in SwiftUI, only RandomAccessCollections
ForEach(Array(strings.enumerated())) { info in
MyObject(info.element, info.offset)
} // Error: Tuples don't conform to Identifiable, the model must expose some form of unique id</code></pre>
<p>Because the concept of object identity is critical in SwiftUI, if the position of an item is relevant to the UI, we must architecture our view's state model around this fact and provide/predict it in advance alongside everything else that is relevant to the view:</p>
<pre><code>struct ListIndexable<T>: Identifiable {
let id = ... // How this is implemented depends
// on what the model represents in practice and how it should be updated
let index: Int
let object: T
}</code></pre>
<p>This means that trying to add new behavior to views can result in the <i>entire architecture</i> of that view needing to be refactored, which is a trade-off that developers creating complicated apps would want to avoid at all costs.</p>
<p>But before we continue, we must mention something very important in this area. Astute readers that have used SwiftUI before might look at this example and point out that you don't really need to do any of these, because there's a variation of <code>ForEach</code> that allows you to manually provide the <code>Hashable</code> value that SwiftUI will use deep-down to enforce uniqueness. This allows you to skip the <code>Identifiable</code> requirement and solve the <code>.enumerated()</code> problem with zero extra code, and if you look up this problem in StackOverflow you can even find plug-and-play extensions for this exact use-case:</p>
<pre><code>private func ForEachEnumerated<T: Hashable, U: View>(_ arr: Binding<[T]>, @ViewBuilder content: @escaping (Int, Binding<T>) -> U) -> some View {
let arr = Array(arr.enumerated())
return ForEach(arr, id: \.offset, content: content)
}</code></pre>
<p><b>You need to be very careful when using snippets like this in SwiftUI</b>, because trying to out-smart the framework is a very easy way to end up with cryptic and undebuggable rendering problems. In this case the problem with the snippet is that using the index itself as the definition of uniqueness has serious implications for how SwiftUI will draw updates to that particular list, which might not be what you're expecting for your particular use-case. This is the same reason why I've refrained from implementing <code>let id</code> in the examples above.</p>
<p>When working with frameworks like SwiftUI and React, you should always avoid shortcuts and take the extra time to think how to architect your view's state in a static and predictable way. I've found that this forces you to put quite a lot of pre-thinking into what you want a certain view to do, because if you forget something down the line it can be very hard to recover from it. This won't be a big deal for simple products, but if you're developing a very complicated app, you might not be willing to make that trade-off.</p>
<h3>Rendering issues are impossible to debug</h3>
<p>Almost nothing you write in SwiftUI is "real" Swift code. With <a href="https://swiftrocks.com/inside-swiftui-compiler-magic">Property Wrappers and Function Builders</a> at its core, almost everything you write will result in the compiler generating additional wiring code for you.</p>
<p>The fact that SwiftUI relies heavily on generated code is not a problem by itself, because as long you know what that wiring code is, you can generally follow along and debug issues just as if that wasn't the case. When the wiring code leads to closed source code however, the story is different.</p>
<p>Here's an example of how this can negatively affect your work. In Burnout Buddy, it's possible to shortcut your way into configuring time schedules for an entire week by setting just one of them and asking the tool to copy the values into the remaining days. In code, I attempted to achieve this by creating a custom <code>Binding</code> object that pastes Monday's schedule into the rest of the week:</p>
<pre><code>let weekdayBinding = Binding<[TimeRange]> {
schedule.days[0]
} set: { range, transaction in
schedule.days[0] = range
schedule.days[1] = range
schedule.days[2] = range
schedule.days[3] = range
schedule.days[4] = range
}</code></pre>
<p>The code above however did not work. To be very specific, it does quite literally nothing. While I could breakpoint the <code>set</code> closure and see that it was being called correctly with the right values, the view was not being updated accordingly. To make things worse, I was also seeing other completely unrelated things being updated as a result of this code that made no sense whatsoever!</p>
<p>The reason it didn't work? I don't know, because <code>Binding</code> is closed source. It took me multiple hours to even realize that this particular piece of code was the source of the problem as there was no feedback from SwiftUI itself that there was something wrong, meaning the only thing I could do was re-write parts of the app aimlessly and hope for the best.</p>
<p>I eventually managed to solve the problem by slightly re-shaping how the closure is executed. Instead of setting each array entry one by one, I chose to copy the entire structure, change it accordingly, and then apply it back to the original bound state object. Because I couldn't actually debug the problem, I can only speculate that the bug has something to do with <code>State</code> not being able to process more than one update in a single stack frame.</p>
<pre><code>let weekdayBinding = Binding<[TimeRange]> {
schedule.days[0]
} set: { range, transaction in
var days = schedule.days
days[0] = range
days[1] = range
days[2] = range
days[3] = range
days[4] = range
schedule.days = days
}</code></pre>
<p>This is not a problem with SwiftUI itself, but with Apple deciding to hide critical logic (in this case, what causes a view to re-render) from you. In contrast, you'd not see problems like this in UIKit because the important bits are always handled on your side of the field (e.g when to call <code>addSubview</code>, <code>layoutIfNeeded</code>, etc).</p>
<p>The good news is that SwiftUI is already able to warn you when you make a rendering mistake in certain cases, so Apple is definitely aware of this problem and is working on ways to make it less disruptive.</p>
<h2>Should I learn SwiftUI?</h2>
<p>Ever since the dawn of software engineering, there have been people engaging in "platform wars". They'll evangelize for the "best architecture", "best framework", "best OS", "best design pattern", and claim that theirs is the only option and everything else is a mistake. This is no different for iOS development, and "SwiftUI vs UIKit" is, sure enough, the new "conflict' of choice.</p>
<p>As a software engineer, it's critical for you to realize that there is no such thing as "best X". Everything is a tool, and whether or not you should use them depends only on what you're trying to achieve. Even Objective-C still has its uses! (If you want more examples of that, <a href="https://swiftrocks.com/there-is-no-right-or-wrong-in-software-engineering">I've written a full article about how there is no right or wrong in tech.</a>)</p>
<p>SwiftUI and UIKit do not cancel each other; they are both good and bad in different ways, and whether or not you should pick SwiftUI specifically will depend on the details of what you're trying to build.</p>
<p>After building a SwiftUI app myself, these are the guidelines that I would personally follow for my next projects:</p>
<ul>
<li><b>Simple UI, Simple Logic:</b> Would make it 100% with SwiftUI.</li>
<li><b>Simple UI, Not-so-simple Logic:</b> I'd start with a 100% SwiftUI app, but if it got too tough I'd fall back to using SwiftUI for the UI itself and UIKit for the more complex pieces of logic (via <code>UIViewRepresentable</code>).</li>
<li><b>Not-so-simple UI, Simple Logic:</b> Will greatly depend on what's being built. I find that more complex pieces of UI can be a lot harder to pull off with SwiftUI when compared to UIKit.</li>
<li><b>Not-so-simple UI, Not-so-simple Logic:</b> Probably best to go 100% UIKit to avoid issues in the long run.</li>
</ul>
<h2>What should beginners learn first?</h2>
<p>I think the situation right now is very similar to when Swift was released. I started with iOS exactly when that happened, and the way I learned it was by first learning as much I could about Objective-C and then using that knowledge to seamlessly move over to Swift and making that my main tool of choice. As the years went by and everyone migrated to Swift, it became less and less necessary to strictly learn Objective-C to the point where nowadays you can defer learning it until you reach a point where you want/need to expand your knowledge of the platform itself.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>This is how I think beginners should approach SwiftUI vs UIKit. While people will always want to learn the shiny new thing, you should always learn both whilst focusing on what the majority of people use right now to maximize your hiring opportunities. I expect that as the years go the majority of UIKit projects will move over to SwiftUI, but it's still too early to tell if UIKit will completely go away given SwiftUI's current issues when used in more complicated projects.</p>
</div>
</div>
<div class="blog-post footer-main">
<div class="footer-logos">
<a href="https://swiftrocks.com/rss.xml"><i class="fa fa-rss"></i></a>
<a href="https://hachyderm.io/@rockbruno"><svg class="svg-logo-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M433 179.1c0-97.2-63.7-125.7-63.7-125.7-62.5-28.7-228.6-28.4-290.5 0 0 0-63.7 28.5-63.7 125.7 0 115.7-6.6 259.4 105.6 289.1 40.5 10.7 75.3 13 103.3 11.4 50.8-2.8 79.3-18.1 79.3-18.1l-1.7-36.9s-36.3 11.4-77.1 10.1c-40.4-1.4-83-4.4-89.6-54a102.5 102.5 0 0 1 -.9-13.9c85.6 20.9 158.7 9.1 178.8 6.7 56.1-6.7 105-41.3 111.2-72.9 9.8-49.8 9-121.5 9-121.5zm-75.1 125.2h-46.6v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.3V197c0-58.5-64-56.6-64-6.9v114.2H90.2c0-122.1-5.2-147.9 18.4-175 25.9-28.9 79.8-30.8 103.8 6.1l11.6 19.5 11.6-19.5c24.1-37.1 78.1-34.8 103.8-6.1 23.7 27.3 18.4 53 18.4 175z"/></svg></a>
<a href="https://twitter.com/rockbruno_"><i class="fa fa-twitter"></i></a>
<a href="https://github.com/rockbruno"><i class="fa fa-github"></i></a>
</div>
<div class="footer-text">
© 2026 Bruno Rocha
</div>
<div class="footer-text">
<p><a href="https://swiftrocks.com">Home</a> / <a href="blog">See all posts</a></p>
</div>
<div class="footer-text">
No AI has ever been used in this blog. Everything down to the HTML/CSS has been handcrafted by me.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Blog Post (Right Sidebar) End -->
</div>
</div>
</div>
<!-- All Javascript Plugins -->
<script type="text/javascript" src="js/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/prism5.js"></script>
<!-- Main Javascript File -->
<script type="text/javascript" src="js/scripts30.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-H8KZTWSQ1R"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-H8KZTWSQ1R');
</script>
</body>
</html>