<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Fresh Beginning]]></title><description><![CDATA[Things I Know and Love to Write About]]></description><link>https://jayeshkawli.com/</link><image><url>https://jayeshkawli.com/favicon.png</url><title>Fresh Beginning</title><link>https://jayeshkawli.com/</link></image><generator>Ghost 5.45</generator><lastBuildDate>Mon, 20 Apr 2026 00:07:22 GMT</lastBuildDate><atom:link href="https://jayeshkawli.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Announcing my Clothing Shop - Burnousstraat Designs]]></title><description><![CDATA[Announcing my new shop Burnousstraat Design where we design graphics apparel. Visit our shop now and enjoy the vibrant collection of apparel]]></description><link>https://jayeshkawli.com/announcement-burnousstraat-designs-shop/</link><guid isPermaLink="false">65fb2b5ebb635b04b003e00f</guid><category><![CDATA[Shop]]></category><category><![CDATA[Clothing]]></category><category><![CDATA[Burnousstraat Designs]]></category><category><![CDATA[Etsy]]></category><category><![CDATA[Shopify]]></category><category><![CDATA[Announcement]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Wed, 20 Mar 2024 19:25:03 GMT</pubDate><media:content url="https://jayeshkawli.com/content/images/2024/03/Home-Page-Banner-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://jayeshkawli.com/content/images/2024/03/Home-Page-Banner-1.png" alt="Announcing my Clothing Shop - Burnousstraat Designs"><p></p><blockquote><strong>Life is too short to wear boring clothes - Anonymous</strong></blockquote><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://jayeshkawli.com/content/images/2024/03/Clothes-collage-for-website.png" class="kg-image" alt="Announcing my Clothing Shop - Burnousstraat Designs" loading="lazy" width="2000" height="1143" srcset="https://jayeshkawli.com/content/images/size/w600/2024/03/Clothes-collage-for-website.png 600w, https://jayeshkawli.com/content/images/size/w1000/2024/03/Clothes-collage-for-website.png 1000w, https://jayeshkawli.com/content/images/size/w1600/2024/03/Clothes-collage-for-website.png 1600w, https://jayeshkawli.com/content/images/size/w2400/2024/03/Clothes-collage-for-website.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Some hand-picked collection from our shop</figcaption></figure><p>Hey folks, this is going to be a slightly different post than usual. I&apos;ve got some exciting news to share with you all &#x2013; we&apos;ve officially launched Burnousstraat Designs, your new go-to destination for one-of-a-kind and unique graphic apparel!</p><p>At Burnousstraat Designs, we&apos;re all about celebrating creativity and self-expression through our unique designs. Whether you&apos;re looking to make a bold statement with your outfit or add a touch of personality to your wardrobe, we&apos;ve got you covered.</p><p>What sets Burnousstraat Designs apart? We specialize in designing and creating graphic apparel that&apos;s not only stylish but also high-quality and comfortable to wear. From eye-catching graphic tees to trendy hoodies and more, each piece in our collection is carefully crafted with attention to detail and a passion for design.</p><p>But that&apos;s not all &#x2013; what makes Burnousstraat Designs truly unique is our commitment to customer satisfaction. We handle everything from design to manufacturing and shipping, ensuring a seamless and hassle-free shopping experience for our customers. So all you have to do is browse our selection, place your order, and wait for your stylish new threads to arrive at your doorstep!</p><p>We are a lifestyle brand and we design for variety of clothings such as hoodies, sweatshirts, oversized shirts, etc. We keep releasing new designs every week, so stay tuned for new collections. You can view them on Shopify under <a href="https://burnousstraatdesigns.com/collections/new-arrivals?ref=jayeshkawli.com">New Arrivals</a> and on Etsy under <a href="https://www.etsy.com/shop/BurnousstraatDesigns?ref=shop-header-name&amp;listing_id=1697282389&amp;from_page=listing&amp;sort_order=date_desc&amp;section_id=48332086">this</a> section.</p><p>You can find us on <a href="https://www.etsy.com/shop/BurnousstraatDesigns?ref=jayeshkawli.com">Etsy</a> and on our <a href="https://burnousstraatdesigns.com/?ref=jayeshkawli.com">own website</a>, where you&apos;ll discover a curated collection of apparel that&apos;s sure to turn heads and spark conversations. And with new designs added regularly, there&apos;s always something fresh and exciting to explore at our shop.</p><p>I am beyond excited to embark on this journey with you all and can&apos;t wait to see how you rock your Burnousstraat Designs apparel. So go ahead, explore our collection, and let your style shine!</p><p>We are also active on <a href="https://www.instagram.com/burnousstraatdesigns/?ref=jayeshkawli.com">Instagram</a> where we frequently publish our new designs. If you like any of them and would like to order, just let us know by commenting and we will work together to place an order and ensure smooth delivery.</p><p>If you have any questions, feedback, or comments, please let me know through <a href="https://burnousstraatdesigns.com/pages/contact?ref=jayeshkawli.com">the shop website</a> or via <a href="https://form.123formbuilder.com/6092023/form?ref=jayeshkawli.com">this contact form</a>. If there is a design you like but isn&apos;t available in a specific type of apparel, color, or size, please let me know. I will take responsibility and do my best to make it available and ship it to your doorstep within the promised delivery window.</p><p>I am excited to be embarking on this journey, and I can&apos;t wait to see big things happen to my small business</p><p>Cheers~~<br>Jayesh Kawli.</p><h4 id="here-are-direct-links-to-online-shops">Here are direct links to online shops:</h4><p><a href="https://www.etsy.com/shop/BurnousstraatDesigns?ref=jayeshkawli.com">Etsy</a></p><p><a href="https://burnousstraatdesigns.com/?ref=jayeshkawli.com">Website</a></p><p><a href="https://www.instagram.com/burnousstraatdesigns/?ref=jayeshkawli.com">Instagram</a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Using RxSwift to Populate Data on UITableView (iOS and Swift)]]></title><description><![CDATA[Learn how to use RxSwift with iOS and Swift to display data on UITableView. Reactive programming with Swift. Learn apply data tableview RxSwift]]></description><link>https://jayeshkawli.com/using-rxswift-with-tableview-ios/</link><guid isPermaLink="false">653a6c4b5ef8fb04b706ac89</guid><category><![CDATA[iOS]]></category><category><![CDATA[RxSwift]]></category><category><![CDATA[Advanced Swift]]></category><category><![CDATA[Swift]]></category><category><![CDATA[Exercise]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Thu, 26 Oct 2023 15:59:58 GMT</pubDate><media:content url="https://jayeshkawli.com/content/images/2023/10/Screenshot-2023-10-26-at-7.40.38-PM.png" medium="image"/><content:encoded><![CDATA[<img src="https://jayeshkawli.com/content/images/2023/10/Screenshot-2023-10-26-at-7.40.38-PM.png" alt="Using RxSwift to Populate Data on UITableView (iOS and Swift)"><p>It&apos;s been a while since I posted. But better late than never. In today&apos;s blog post, I am going to write about how to use RxSwift to populate data in <code>UITableView</code> on iOS.</p><blockquote>RxSwift is a library for composing asynchronous and event-based code by using observable sequences and functional style operators, allowing for parameterized execution via schedulers. (from <a href="https://www.kodeco.com/books/rxswift-reactive-programming-with-swift/v4.0/chapters/1-hello-rxswift?ref=jayeshkawli.com">Kodeco.com</a>)</blockquote><h3 id="pre-requisites">Pre-requisites</h3><p>Before we get started with <code>RxSwift</code>, you need to add <code>RxSwift</code> dependencies in your Xcode project via Cocoapods (Or any other suitable dependency management system on iOS). For my project specification, &#xA0;<code>podfile</code> looks like this,</p><pre><code class="language-ruby">

target &apos;TravelPlanner&apos; do
    use_frameworks!

    pod &apos;RxSwift&apos;
    pod &apos;RxCocoa&apos;
    pod &apos;RxSwiftExt&apos;
    pod &apos;RxDataSources&apos;

end

</code></pre><p>After you save this file, run <code>pod install</code> from the command line and open <code>&lt;project_name&gt;.xcworkspace</code> file.</p><h3 id="building-blocks">Building Blocks</h3><p>In order to build an app that can download and apply data to <code>UITableView</code> through <code>RxSwift</code>, we are going to start with basic building blocks which will form the foundation of the entire app we are building.</p><h4 id="building-views">Building Views</h4><p>The first thing we are going to do is build views. To keep it short and simple, I am not going to build everything from scratch, but we will make assumptions about the layout as we go along. </p><p>For our example, we are using a search bar and a table view. As the user is typing in the search bar, the app will input the entered text and send a request for locations matching with input text.</p><pre><code class="language-swift">
import UIKit
import RxSwift
import RxDataSources

final class SearchViewController: UIViewController {

    let dataSource: RxTableViewSectionedReloadDataSource&lt;SectionModel&lt;String, Station&gt;&gt;

    private lazy var stackView: UIStackView = {
        let view = UIStackView()
        view.axis = .vertical
        return view
    }()

    private lazy var searchBar: UISearchBar = {
        UIBarButtonItem.appearance(whenContainedInInstancesOf:[UISearchBar.self]).tintColor = .white
        let bar = UISearchBar()
        bar.placeholder = &quot;Waar wilt u naartoe? (minimaal 2 karakters)&quot;
        bar.searchTextField.backgroundColor = .white
        bar.barTintColor = .black
        return bar
    }()

    private lazy var searchResultsTableView: UITableView = {
        let view = UITableView()
        view.register(UITableViewCell.self, forCellReuseIdentifier: &quot;Station&quot;)
        return view
    }()

    init() {
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError(&quot;init(coder:) has not been implemented&quot;)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // An utility method to set up views
        setupViews()

        // An utility method to set up view constraints
        setupConstraints()
    }

    //MARK: Private methods
    private func setupViews() {
        // Set up views
    }

    private func setupConstraints() {
        // Set up constraints
    }
}

</code></pre><p>After we run the app, this is how our UI will look like,</p><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/10/Screenshot-2023-10-26-at-3.53.24-PM.png" class="kg-image" alt="Using RxSwift to Populate Data on UITableView (iOS and Swift)" loading="lazy" width="308" height="579"></figure><h4 id="building-a-codable-station-struct">Building a Codable Station struct</h4><p>As the downloaded location object represents the train station, we will create a new <code>Codable</code> struct called <code>Station</code>. </p><pre><code class="language-swift">

struct Station: Decodable {
    let code: String
    let fullName: String
}


</code></pre><h4 id="building-a-network-service">Building a Network Service</h4><p>Since we need to download data from the network, we will build a mock network service. This is just a dummy service for example, but you can replace this part with the actual service that is responsible for making a network call.</p><pre><code class="language-swift">
import Foundation

import RxSwift
import RxCocoa

struct StationsService {

    private let urlSession: URLSession
    private let decoder: JSONDecoder

    init(urlSession: URLSession = .shared, decoder: JSONDecoder = JSONDecoder()) {
        self.urlSession = urlSession
        self.decoder = decoder
    }

    func searchStations&lt;T: Decodable&gt;(for type: T.Type, route: APIRoute) -&gt; Observable&lt;[T]&gt; {
        return urlSession.rx.data(request: ..&lt;url_request&gt;).map { data -&gt; T in
            do {
                return try decoder.decode([T].self, from: data)
            }
        }
    }
}


</code></pre><h4 id="building-a-view-model">Building a View Model</h4><p>In order to support downloading and displaying locations, we will use the view model and inject it into a view controller.</p><p>The view model is responsible for following tasks</p><ol><li>Intercepting input text and sending a network request to download the list of locations with the search query</li><li>Converting downloaded location models into section view models and notifying the view to update the table view with these view models</li></ol><p>The view model will have two extra structs inside it. One for input and another for output. Input will observe for any change in the search text and Output will publish any changes with station sections.</p><pre><code class="language-swift">
import Foundation

import RxSwift
import RxDataSources

final class SearchViewModel {
    var input: Input?
    var output: Output?

    struct Input {
        // Input search text
        let searchText: AnyObserver&lt;String?&gt;
    }

    struct Output {
        // List of station sections which are returned based on the search text entered by user
        let stationSections: Observable&lt;[SectionModel&lt;String, Station&gt;]&gt;
    }    

    private let stationsService: StationsService

    init(stationsService: StationsService) {


        let searchText = PublishSubject&lt;String?&gt;()
        self.stationsService = stationsService

        //We are delaying sending request for input by 0.5 seconds
        let stations = searchText
            .debounce(.milliseconds(500), scheduler: MainScheduler.instance)
            .flatMapLatest { searchText -&gt; Observable&lt;[Station]&gt; in

                guard let searchText, searchText.count &gt; 2 else {
                    return .just([])
                }
                return stationsService.searchStations(for: [Station].self, route: .searchStations(query: searchText))
            }.catchError { [weak self] error in
                self?.errorMessageSubject.onNext(error.localizedDescription)
                return .just([])
            }

        let stationSections = Observable.combineLatest(stations, searchText).map { [weak self] (elements, searchText) -&gt; [SectionModel&lt;String, Station&gt;] in

            guard let self else { return [] }

            return [SectionModel(model: &quot;Current Search&quot;, items: elements)]
        }

        input = Input(
            searchText: searchText.asObserver()
        )

        output = Output(
            stationSections: stationSections
        )
    }
}


</code></pre><p>As you can see, there is a lot going on in the view model. First, we initialize the view model with the network service object. Then, we have an observer on the input search text. To avoid making duplicate and repetitive requests, we have added a delay of 500ms every time the user types in the text.</p><p>As soon as the text is entered and captured, we make a call to the network service with the input text. Service responds with the list of <code>Station</code> objects which is then mapped to a list of <code>SectionModel</code> objects. Each section model represents a table view section and associated list of station locations.</p><h4 id="adding-bindings-in-view-controller">Adding Bindings in View Controller</h4><p>Now we are done with view model bindings. In the next part, we see how to perform <code>RxSwift</code> bindings in the view controller. </p><p>View controller bindings are done for two reasons,</p><ol><li>To bind the input search text to <code>searchText</code> observer property on the view model&apos;s input struct</li><li>To bind the view model-provided station sections to the data source and attach it to the table view to populate the list data</li></ol><blockquote>Please note that we will also need a <code>DisposeBag</code> object to store observable otherwise those are immediately released from memory and updates will be lost</blockquote><p>Here&apos;s the partial source code for bindings,</p><pre><code class="language-swift">
.....
...
private let bag = DisposeBag()
..
....

init() {
	....
    ..
    
    self.dataSource = RxTableViewSectionedReloadDataSource&lt;SectionModel&lt;String, Station&gt;&gt;(
            configureCell: { (_, tv, indexPath, element) in
                let cell = tv.dequeueReusableCell(withIdentifier: &quot;Station&quot;)!
                cell.textLabel?.text = element.name.fullName
                return cell
            },
            titleForHeaderInSection: { dataSource, sectionIndex in
                return dataSource[sectionIndex].model
            }
        )
}

private func setupRx() {

    guard let input = viewModel.input, let output = viewModel.output else { return }

    searchBar.rx
            .value.orEmpty
            .bind(to: input.searchText)
            .disposed(by: bag)

    output
            .stationSections
            .bind(to: searchResultsTableView.rx.items(dataSource: dataSource))
            .disposed(by: bag)
}



</code></pre><blockquote>As you can see, with the <code>RxSwift</code>, we saved on a lot of boilerplate code by eliminating routine table view datasource and delegate methods and replacing them with the <code>RxTableViewSectionedReloadDataSource</code> object</blockquote><h3 id="running-the-app-and-verifying-the-results">Running the App and Verifying the Results</h3><p>Now that all the building blocks are ready, let&apos;s run the app and see the result.</p><figure class="kg-card kg-video-card"><div class="kg-video-container"><video src="https://jayeshkawli.com/content/media/2023/10/Screen-Recording-2023-10-26-at-4.20.37-PM.mp4" poster="https://img.spacergif.org/v1/628x1190/0a/spacer.png" width="628" height="1190" playsinline preload="metadata" style="background: transparent url(&apos;https://jayeshkawli.com/content/images/2023/10/media-thumbnail-ember529.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div></figure><p>As you can see, when the app starts for the first time, no results are displayed. As they start typing in the search bar, the text is recognized with a 500ms delay, the network request is sent with the search keyword and search results are populated in the <code>TableView</code>.</p><h3 id="conclusion">Conclusion</h3><p>So that was all about how to use RxSwift to show data on <code>UITableView</code> in iOS using Swift. Hope this tutorial was helpful to you. RxSwift is a reactive iOS framework that is responsible for implicitly performing many operations. We saw one such operation - Applying data to table view via data sources without using iOS&apos;s native data source and delegate conventions. </p><p>If you have any questions or concerns about this article, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><h3 id="support-and-feedback">Support and Feedback</h3><p></p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation for me to keep writing more articles like this.</p><h3 id="consulting-services">Consulting Services</h3><p>I also provide a few consulting services on Topmate.io, and you can reach out to me there too. These services include,</p><ol><li><a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">Let&apos;s Connect</a></li><li><a href="https://topmate.io/jayesh_kawli/62010?ref=jayeshkawli.com">Resume Review</a></li><li><a href="https://topmate.io/jayesh_kawli/62009?ref=jayeshkawli.com">1:1 Mentorship</a></li><li><a href="https://topmate.io/jayesh_kawli/62012?ref=jayeshkawli.com">Interview Preparation &amp; Tips</a></li><li><a href="https://topmate.io/jayesh_kawli/63004?ref=jayeshkawli.com">Conference Speaking</a></li><li><a href="https://topmate.io/jayesh_kawli/215835?ref=jayeshkawli.com">Take-home Exercise Help (iOS)</a></li><li><a href="https://topmate.io/jayesh_kawli/62008?ref=jayeshkawli.com">Career Guidance</a></li><li><a href="https://topmate.io/jayesh_kawli/62011?ref=jayeshkawli.com">Mock Interview</a></li></ol>]]></content:encoded></item><item><title><![CDATA[SwiftUI: Apply Custom Formatting to Localized Strings]]></title><description><![CDATA[How to apply custom formatting to localized strings in SwiftUI. tokenize and apply custom formatting to SwiftUI strings.]]></description><link>https://jayeshkawli.com/swiftui-apply-custom-formatting-to-localized-strings/</link><guid isPermaLink="false">65144bd82b599204c7ba6f0b</guid><category><![CDATA[New SwiftUI APIs]]></category><category><![CDATA[Niche]]></category><category><![CDATA[Advanced SwiftUI]]></category><category><![CDATA[Formatting]]></category><category><![CDATA[Localized]]></category><category><![CDATA[Strings]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Wed, 27 Sep 2023 16:41:53 GMT</pubDate><media:content url="https://jayeshkawli.com/content/images/2023/09/Screenshot-2023-09-27-at-6.41.35-PM.png" medium="image"/><content:encoded><![CDATA[<img src="https://jayeshkawli.com/content/images/2023/09/Screenshot-2023-09-27-at-6.41.35-PM.png" alt="SwiftUI: Apply Custom Formatting to Localized Strings"><p>In today&apos;s post, I am going to touch on a niche area in SwiftUI designs and string formatting. This is about how to apply custom formatting to localizable strings. It sounds complicated, so let me explain it with an example,</p><p>Suppose I have a localizable string like this,</p><p><code>Going from %1$@ to %2$@. Have a happy journey passing via %3$@. Bon Voyage</code></p><blockquote>Please note that this strings is likely to be translated in many languages where positional parameters might get tossed around in unpredictable manner. For example, in some languages station names might appear before &quot;Going from&quot; phrase or the station name that represents passing via argument may appear in the middle instead of at the end like this sentence in English.</blockquote><p>The problem statement is, in the above example, we want to apply special formatting only to arguments. For example, station names must appear in bold form with red color in the background and white in the foreground. </p><p>One option is to extract arguments by start and end index and selectively apply styles to them. However, this approach won&apos;t work for apps supporting localization.</p><p>We are going to achieve this by recognizing literal arguments and applying selective formatting to them. Let&apos;s see how!</p><ol><li>We will use the input string which contains regular text and arguments that need formatting. We will sequence arguments in the format <code>%i$@</code> where <code>i</code> represents their position in the sequence and it starts with 1. For example, <code>Going from %1$@ to %2$@. Have a happy journey passing via %3$@. Bon Voyage</code></li><li>We will create a function that will take the input string and sequence of passed arguments replacing placeholder symbols (e.g. <code>%1$@</code>)</li><li>We will create another utility function which will take these parameters and return the array of <code>FragmentType</code>. Each fragment type represents either a literal or string</li><li>We will go over an array of <code>FragmentType</code> and apply styles to arguments (Or literals if that is the requirement)</li><li>We will combine the array of <code>Text</code> objects from the previous step into a single <code>Text</code> objects and display them on the <code>SwiftUI view</code></li></ol><p>First, we will define an enum to store fragments and literal from the input string</p><pre><code class="language-swift">
enum FragmentType {
    case literal(String)
    case argument(Int)
}

</code></pre><p>Next, we will write functions that take an input string and convert it into an array of <code>FragmentType</code> enums for later formatting.</p><pre><code class="language-swift">
    static func fragmentizeFormattedString(_ formatString: String) -&gt; [FragmentType] {
        return getFragments(regex: &quot;%\\d+\\$@&quot;, inputText: formatString)
    }

    static func getFragments(regex: String, inputText: String) -&gt; [FragmentType] {

        var fragments: [FragmentType] = []

        guard let regex = try? NSRegularExpression(pattern: regex) else {
            return []
        }
        let results = regex.matches(in: inputText,
                                range: NSRange(inputText.startIndex..., in: inputText))

        var previousIndex = inputText.startIndex

        var currentIndex = 0
        for match in results {
            for range in 0..&lt;match.numberOfRanges {
                let rangeBounds = match.range(at: range)

                guard let range = Range(rangeBounds, in: inputText) else {
                    continue
                }

                if range.lowerBound != inputText.startIndex {
                    let closedRange = previousIndex..&lt;range.lowerBound
                    fragments.append(.literal(String(inputText[closedRange])))
                }
                fragments.append(.argument(currentIndex))
                previousIndex = range.upperBound
                currentIndex += 1
            }
        }

        if previousIndex &lt; inputText.endIndex {
            fragments.append(.literal(String(inputText[previousIndex..&lt;inputText.endIndex])))
        }

        return fragments
    }
    
    </code></pre><p>There is a lot going on in the previous function. It accepts an input string along with a regular expression to extract arguments of type <code>%1$@</code> from it. This is dictated by the regular expression. As it moves along, it breaks the original string into literal (non-arguments) and arguments. Literals are converted into <code>literal</code> case with text as an argument and arguments into <code>argument</code> case with argument index as an argument.</p><p>In the next part, we will see how to use these fragments for formatting specific parts of the string and presenting them on the <code>SwiftUI</code> view.</p><pre><code class="language-swift">
struct CustomStringFormat_Example: View {

    let inputString = &quot;Going from %1$@ to %2$@. Have a happy journey passing via %3$@. Bon Voyage&quot;

    let arguments = [&quot;Boston&quot;, &quot;New York&quot;, &quot;Providence&quot;]

    var body: some View {
        VStack {
            let fragments = fragmentizeFormattedString(inputString)

            let output = fragments.map { fragment in
                switch fragment {
                case .literal(let staticText):
                    return Text(staticText).foregroundColor(Color.red).font(.largeTitle)
                case .argument(let index):
                    return Text(arguments[index]).foregroundColor(Color.yellow).font(.title)
                }
            }.reduce(Text(verbatim: &quot;&quot;), +)

            output
        }
    }
}

</code></pre><p>In the code above, we simply iterated over a list of output fragments and applied formatting one by one. Let&apos;s take a look at examples,</p><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/09/Screenshot-2023-09-27-at-6.29.52-PM.png" class="kg-image" alt="SwiftUI: Apply Custom Formatting to Localized Strings" loading="lazy" width="612" height="316" srcset="https://jayeshkawli.com/content/images/size/w600/2023/09/Screenshot-2023-09-27-at-6.29.52-PM.png 600w, https://jayeshkawli.com/content/images/2023/09/Screenshot-2023-09-27-at-6.29.52-PM.png 612w"></figure><p>If you want to keep literal styles unchanged, you can also skip formatting on them.</p><pre><code class="language-swift">
case .literal(let staticText):
    return Text(staticText)
    
</code></pre><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/09/Screenshot-2023-09-27-at-6.34.25-PM.png" class="kg-image" alt="SwiftUI: Apply Custom Formatting to Localized Strings" loading="lazy" width="604" height="202" srcset="https://jayeshkawli.com/content/images/size/w600/2023/09/Screenshot-2023-09-27-at-6.34.25-PM.png 600w, https://jayeshkawli.com/content/images/2023/09/Screenshot-2023-09-27-at-6.34.25-PM.png 604w"></figure><h3 id="localization">Localization</h3><p>You might have noticed that I talked a lot about localization in this post. The reason for using formatted strings with arbitrary arguments is to facilitate the localization process. For example, if the arguments are embedded into the string with no distinction between literals and arguments, it can easily lose context and produce a garbled translation. </p><p>Referring to the above example, each language will have its own rules to position station names within the input string. Since we&apos;re passing them by arguments and the context of the string, translators can better place them to match the target languages&apos; rules. </p><p>For example, consider the following line in the Localization file,</p><pre><code class="language-swift">
//A informational string where first two arguments indicate final source and destination stations. The last argument indicates the station through which the train will be passing

 &quot;Going from %1$@ to %2$@. Have a happy journey passing via %3$@. Bon Voyage&quot; = &quot;Going from %1$@ to %2$@. Have a happy journey passing via %3$@. Bon Voyage&quot;;
 
 </code></pre><p>Now, when the translator looks at the comment and the original English string, they know the context of this message, not just by looking at the raw string, but also the situation where these arguments are used in the user-facing message.</p><p>So final translations in some other languages will look like this,</p><pre><code class="language-markdown">
// French
//A informational string where first two arguments indicate final source and destination stations. The last argument indicates the station through which the train will be passing

&quot;Going from %1$@ to %2$@. Have a happy journey passing via %3$@. Bon Voyage&quot; = &quot;Passer de %1$@ &#xE0; %2$@. Bon voyage en passant via %3$@. bon voyage&quot;;

//Spanish
//A informational string where first two arguments indicate final source and destination stations. The last argument indicates the station through which the train will be passing

&quot;Going from %1$@ to %2$@. Have a happy journey passing via %3$@. Bon Voyage&quot; = &quot;Pasando de %1$@ a %2$@. Que tengas un feliz viaje pasando por %3$@. buen viaje&quot;;


//Hindi
//A informational string where first two arguments indicate final source and destination stations. The last argument indicates the station through which the train will be passing

&quot;Going from %1$@ to %2$@. Have a happy journey passing via %3$@. Bon Voyage&quot; = &quot;%1$@ &#x938;&#x947; %2$@ &#x924;&#x915; &#x91C;&#x93E; &#x930;&#x939;&#x93E; &#x939;&#x942;&#x901;&#x964; %3$@ &#x938;&#x947; &#x939;&#x94B;&#x915;&#x930; &#x917;&#x941;&#x91C;&#x930;&#x928;&#x947; &#x935;&#x93E;&#x932;&#x940; &#x906;&#x92A;&#x915;&#x940; &#x92F;&#x93E;&#x924;&#x94D;&#x930;&#x93E; &#x92E;&#x902;&#x917;&#x932;&#x92E;&#x92F; &#x939;&#x94B;&#x964; &#x92C;&#x949;&#x928; &#x92F;&#x93E;&#x924;&#x94D;&#x930;&#x93E;&quot;;

</code></pre><blockquote>As evident from these translations, the absolute position of arguments changes by language, so relying on the arguments rather than their absolute position in English is the right way to apply translations and argument-wise formatting</blockquote><h3 id="source-code">Source Code</h3><p>The full source code for this tutorial is available on <a href="https://gist.github.com/jayesh15111988/604e3e9b3630fdcb8315f8d955b4c879?ref=jayeshkawli.com">GitHub Custom String Format Repo</a> for further reference</p><h3 id="summary">Summary</h3><p>So this was all about how to apply custom styles to localizable strings in SwiftUI. As more and more apps become available worldwide, localization is a requirement and not an option for any modern app. The solution mentioned in this post works well for any range of strings translated into any language. </p><p>Have you faced a similar issue in your production app before? How was your experience and how did you solve it? Does the solution mentioned here fit your use case? If not, what changes do you suggest to make it more robust and flexible? I would love to hear your thoughts.</p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep writing more articles like this.</p><h3 id="consulting-services">Consulting Services</h3><p>I also provide a few consulting services on Topmate.io, and you can reach out to me there too. These services include,</p><ol><li><a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">Let&apos;s Connect</a></li><li><a href="https://topmate.io/jayesh_kawli/62010?ref=jayeshkawli.com">Resume Review</a></li><li><a href="https://topmate.io/jayesh_kawli/62009?ref=jayeshkawli.com">1:1 Mentorship</a></li><li><a href="https://topmate.io/jayesh_kawli/62012?ref=jayeshkawli.com">Interview Preparation &amp; Tips</a></li><li><a href="https://topmate.io/jayesh_kawli/63004?ref=jayeshkawli.com">Conference Speaking</a></li><li><a href="https://topmate.io/jayesh_kawli/215835?ref=jayeshkawli.com">Take-home Exercise Help (iOS)</a></li><li><a href="https://topmate.io/jayesh_kawli/62008?ref=jayeshkawli.com">Career Guidance</a></li><li><a href="https://topmate.io/jayesh_kawli/62011?ref=jayeshkawli.com">Mock Interview</a></li></ol>]]></content:encoded></item><item><title><![CDATA[On Reading and Analyzing the Annual Reports]]></title><description><![CDATA[How I read and analyze annual reports. How to read and understand the company's annual reports. Indian stock market. SENSEX and SEBI]]></description><link>https://jayeshkawli.com/reading-and-analyzing-the-annual-reports/</link><guid isPermaLink="false">64eeeddd2b599204c7ba6dde</guid><category><![CDATA[Annual Report]]></category><category><![CDATA[Company]]></category><category><![CDATA[Finance]]></category><category><![CDATA[Corporate]]></category><category><![CDATA[SEBI]]></category><category><![CDATA[Reports]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Thu, 31 Aug 2023 15:09:30 GMT</pubDate><media:content url="https://jayeshkawli.com/content/images/2023/08/Screenshot-2023-08-31-at-10.39.35-AM.png" medium="image"/><content:encoded><![CDATA[<img src="https://jayeshkawli.com/content/images/2023/08/Screenshot-2023-08-31-at-10.39.35-AM.png" alt="On Reading and Analyzing the Annual Reports"><p>Recently I have taken to reading annual reports of companies - For now, only public Indian companies. My father used to and still trades in stocks and I remember when I was a kid, we used to receive several huge paper reports at the year&apos;s end. Now, thanks to increased awareness about climate change, everything is represented in digital formats.</p><p>As a kid, my favorite activity was to look at the fancy pictures, cut them off, and glue them in my room. Since I started learning English too late, I couldn&apos;t understand a word of it. But it was still fun to look at all the top brass who I knew were the ultimate bosses of the company. Not sure how much anyone got out of those reports at that time, but we definitely made some good money selling those heavy reports as junk paper to local retailers and grocery merchants.</p><p>Now that I have grown up and have educated myself a little bit about good companies, stock markets, and returns, I have turned to reading public companies&apos; annual reports during my leisure time. These documents look daunting in the beginning due to the sheer size, but you don&apos;t have to read every word of it. Most of the report is templated, and some of the content is repeated due to regulatory standards about how the report should be published. </p><p>So how do I read the report? Which things do I pay attention to and which things do I ignore? How do I manage time to read it? How does reading a report change my opinion about the company and ultimately about the investment decision? In this post, I am going to clarify my rationale behind the report reading and how it affects my investment decisions.</p><h3 id="choosing-which-report-to-read">Choosing Which Report to Read</h3><p>There are thousands of companies out there on NSE and BSE. It is virtually impossible for one person to sit and go through all their annual reports. Besides, you won&apos;t get much value when you are curious about everything around you. In my opinion, as far as report reading decision is concerned, companies fall into the following brackets,</p><ol><li>Good companies I haven&apos;t invested in, but I still want to </li><li>Companies I am interested/invested in, but don&apos;t have much clue about</li><li>Companies with an innovative business model</li><li>Companies I am torn between - On one hand, I want to own the stock, but on the other hand, analysts are raking this company over the coal</li></ol><p>For example, companies like Reliance Industries, Tata Motors, TCS, and Tata Coffee are well-known, stable, and have their business models straightforward, so I won&apos;t dig much into them. But if I come across less well-known companies or companies that are also doing some interesting stuff, I satisfy my curiosity by reading their reports. This includes Tata Elxsi, Devyani International, Jubilant Food, Thermax, IndiaMart, etc.</p><h3 id="which-content-do-i-focus-on">Which Content Do I Focus On</h3><p>There is just too much content in the annual report, so reading it all is not practical. I usually focus on,</p><ol><li>Company management (Resignations, scandals, unusual departures, scrutiny, compensation, etc.)</li><li>Has the company suffered from any penalty by the regulatory authority (Imprisonment, fines, lawsuits, etc.)</li><li>Management discussion and Analysis (Introspection and future outlook)</li><li>Known risks and risk mitigation strategies</li><li>Financial statements (Not too deep, but mainly focusing on free cash flow, retained earnings, EBITDA, debt/equity ratio, etc.)</li><li>Employee development (Training, insurance, parental support, ease of mobility, attrition rate, etc.)</li><li>Future expansion plans and major collaborations</li><li>Sustainability approach and renewable energy investments</li></ol><h3 id="which-content-do-i-ignore">Which Content Do I Ignore?</h3><p>I believe SEBI has a pre-defined template for presenting annual reports. No matter what you do, you still have to adhere to it. This includes generic disclosures, legal opinions, annexure, auditors report, notices, e-voting instructions, and disclaimers regarding things out of control of management and the company. </p><p>There is also some content that gets repeated on many pages. For example, SEBI might ask companies to provide answers on two issues, but they both have the same answer. In that case, the company still has to publish it twice. </p><h3 id="vague-statements">Vague Statements</h3><p>Sometimes when asked about risk management, profitability of future outlook, the company makes generic statements that have little or no relation to the business. Reports might also be making claims about the popularity of specific products or services, but if you look up online, people aren&apos;t really happy about them.</p><p>In other cases, customer care of the company is not up to par and people receiving bad products badmouth the company on social media. In such cases, you no longer know which version to believe.</p><h3 id="look-for-explanations">Look for Explanations</h3><p>Companies might not be too lucky to put everything in place all the time. Sometimes things go wrong. For example, reduced profitability, lawsuits from tax authorities, public scandals, impairment of assets, etc. However, the company is still expected to provide satisfactory reasoning for such circumstances no matter how bitter they are.</p><p>As an investor, I would rather be happy to see the company being transparent and providing all the details and reasoning behind unfavorable outcomes or decisions than presenting it as &quot;good&quot; to convince shareholders that it&apos;s not as bad as it looks.</p><h3 id="look-for-exceptional-items">Look for Exceptional Items </h3><p>Exceptional items are those that happen one time in the given financial year. They may significantly affect the profitability or loss. Although financial statements may look worse or better, you might need to check if there are any exceptional items causing it.</p><h3 id="key-audit-matters">Key Audit Matters</h3><p>Key audit matters are those matters that, in the auditor&apos;s professional judgment, were of most significance in the audit of the financial statements. They may be related to asset impairment or tax penalties that might have significantly moved the financial statements.</p><h3 id="summary">Summary</h3><p>No matter how many times you have done it, annual reports are intimidating. They are at least 200 pages long and some can even stretch 550 pages long. Only look at the things that matter to you and focus on them. For example, if the company is already financially strong, you can gloss over its financial statements. If you already know about its operations and business model, you can skip the introductory pages.</p><p>Financial statements are one of the worst parts of a report. Unless you&apos;re an accountant, a lot of this stuff is not going to make sense to you. But you can still understand, company assets, liability, loans, impairment, profit after tax, tax, exceptional items, etc. These are still good things to know to gauge the company&apos;s financial strength. </p><p>Hope you liked this post which deviates a lot from my regular iOS articles. Are you an investor and are avid reader of the annual reports? What was your experience like? Are there things you pay attention to or perceive important that went unmentioned in this article? Please share your thoughts with me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep writing more articles like this.</p>]]></content:encoded></item><item><title><![CDATA[How to Use Property Wrappers in Swift]]></title><description><![CDATA[Learn how to use property wrappers in Swift with examples.  Explained with code.  iOS Property wrappers]]></description><link>https://jayeshkawli.com/how-to-use-property-wrappers-in/</link><guid isPermaLink="false">64eca9ec2b599204c7ba6d07</guid><category><![CDATA[Property Wrapper]]></category><category><![CDATA[Wrapper]]></category><category><![CDATA[Property]]></category><category><![CDATA[Advanced Swift]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[Swift 5.1]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Mon, 28 Aug 2023 17:04:29 GMT</pubDate><content:encoded><![CDATA[<p>Swift 5.1 introduced a concept of property wrapper. Property wrappers is a mechanism that allows developers to centralize code for modifying the variable properties during setting and getting their values.</p><p>For example, as soon as the variable is set, you might want to apply some changes to its property (As in setting <code>translatesAutoresizingMaskIntoConstraints</code> property on <code>UIView</code> instance to false for example) or use a custom transformation and get the transformed value the next time you access it (For example, converting the date string into <code>Date</code> object). </p><p>Property wrappers allow you to create a wrapper for such custom changes, centralize the logic, and return the modified value with complete abstraction. </p><p>Let&apos;s try to understand how property wrappers work with examples,</p><h3 id="setting-translatesautoresizingmaskintoconstraints-to-false-for-uiview-instances">Setting <code>translatesAutoresizingMaskIntoConstraints</code> to false for <code>UIView</code> instances</h3><p>One of the major pet peeves of all iOS developers is that, as soon as they create an <code>UIView</code> instance, they need to set manually <code>translatesAutoresizingMaskIntoConstraints</code> property on it to false. This is not only cumbersome, but sometimes when you forget to do it, your layout will break and the console will throw several warnings for forgetting to set it to false. </p><p>With property wrappers, we can centralize this logic in just one place. As long as any <code>UIView</code> instance is using this wrapper this property will automatically be set to false when it&apos;s first initialized or it is assigned the value of another <code>UIView</code> instance.</p><p>To begin, we will create a new struct with <code>@propertyWrapper</code> annotation to indicate that this struct represents a property wrapper. We are going to use it on any <code>UIView</code> instance so that we will use the generic type <code>T</code> of type <code>UIView</code>. </p><pre><code class="language-swift">
@propertyWrapper
struct Autolayout&lt;T: UIView&gt; {

}</code></pre><p>Now, in the next part, we will add a customization code inside initialization where as soon as the variable is initialized with this annotation, we will set its <code>translatesAutoresizingMaskIntoConstraints</code> property to false. </p><pre><code class="language-swift">
@propertyWrapper
struct Autolayout&lt;T: UIView&gt; {

    var wrappedValue: T

    init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
        _setAutoOff()
    }

    func _setAutoOff() {
        wrappedValue.translatesAutoresizingMaskIntoConstraints = false
    }
}

</code></pre><p>This works fine during initialization. But what if another value is assigned to our <code>UIView</code> instance after initialization and that property associated with that value isn&apos;t decorated with <code>Autolayout</code> annotation? </p><p>In that case, we need to detect any later change in property with <code>didSet</code> observer and call <code>_setAutoOff</code> to manually change the value of <code>translatesAutoresizingMaskIntoConstraints</code> to <code>false</code>.</p><pre><code class="language-swift">
@propertyWrapper
struct Autolayout&lt;T: UIView&gt; {

    var wrappedValue: T {
        didSet {
            _setAutoOff()
        }
    }

    init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
        _setAutoOff()
    }

    func _setAutoOff() {
        wrappedValue.translatesAutoresizingMaskIntoConstraints = false
    }
}

</code></pre><h3 id="demo">Demo</h3><p>Now that our property wrapper is ready, let&apos;s test it. </p><p>First, we will create an <code>UIView</code> instance without <code>Autolayout</code> annotation and another example with annotation. Then we will verify <code>translatesAutoresizingMaskIntoConstraints</code> property on both of them. The value of this property should be true on the former and false on the latter.</p><pre><code class="language-swift">
var viewWithoutPropertyWrapperAnnotation = UIView(frame: .zero)
@Autolayout var viewWithPropertyWrapperAnnotation = UIView(frame: .zero)

func testThatAutolayoutPropertyWrapperWorksAsExpected() {
    XCTAssertTrue(viewWithoutPropertyWrapperAnnotation.translatesAutoresizingMaskIntoConstraints)
    XCTAssertFalse(viewWithPropertyWrapperAnnotation.translatesAutoresizingMaskIntoConstraints)
}


</code></pre><p>If we run the tests, we will find that the tests passed successfully.</p><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/08/Screenshot-2023-08-28-at-5.57.47-PM.png" class="kg-image" alt loading="lazy" width="1312" height="306" srcset="https://jayeshkawli.com/content/images/size/w600/2023/08/Screenshot-2023-08-28-at-5.57.47-PM.png 600w, https://jayeshkawli.com/content/images/size/w1000/2023/08/Screenshot-2023-08-28-at-5.57.47-PM.png 1000w, https://jayeshkawli.com/content/images/2023/08/Screenshot-2023-08-28-at-5.57.47-PM.png 1312w" sizes="(min-width: 720px) 720px"></figure><p>Let&apos;s look at another example with a slight variant to show the application of property wrappers.</p><h3 id="converting-string-to-formatted-date-string">Converting String to Formatted date string</h3><p>In this example, we will create a custom property wrapper to convert a given string into a pre-defined date format string. If the input string is a valid date, we will return the input string. If it does not represent a valid date, our property wrapper will return the <code>nil</code> value.</p><p>Since we are only dealing with strings, a property wrapper will take initial value that conforms to <code>StringProtocol</code>. During initialization, it will validate the string and assign the input string to the private property representing the variable or nil, if the input string is not a valid date.</p><pre><code class="language-swift">
@propertyWrapper
struct DateFormatterFilter&lt;T: StringProtocol&gt; {

    private var _value: T?

    init(wrappedValue value: T?) {
        self._value = self.filterInputDateString(value)
    }

    var wrappedValue: T? {
        get {
            return filterInputDateString(_value)
        }
        set {
            _value = newValue
        }
    }

    private func filterInputDateString(_ value: T?) -&gt; T? {
        let dateFormatter = DateFormatter()

        dateFormatter.dateFormat = &quot;yyyy-MM-dd&quot;
        if let stringValue = value as? String, let _ = dateFormatter.date(from: stringValue) {
            return value
        }
        return nil
    }
}

</code></pre><p>Please note that we are calling <code>filterInputDateString</code> twice. Once during initalization and other time when we update the value of variable under property wrapper.</p><p>Now that the wrapper is ready, let&apos;s write some tests to verify whether it functions as expected or not. </p><p>We will use valid and invalid dates to make verify the property wrapper produces correct output. We will also use one more invalid date string without a property wrapper. Since it won&apos;t undergo formal validation, it should always have a valid value irrespective whether it represents a valid date or not.</p><pre><code class="language-swift">
@DateFormatterFilter var validDateString = &quot;1990-10-20&quot;
@DateFormatterFilter var invalidDateString = &quot;1990-100-20&quot;
var invalidDateStringWithNoWrapper = &quot;1990-10-200&quot;

func testThatDateFormatterFilterPropertyWrapperWorksAsExpected() {
    XCTAssertNotNil(validDateString)
    XCTAssertNil(invalidDateString)
    XCTAssertNotNil(invalidDateStringWithNoWrapper)
}

</code></pre><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/08/Screenshot-2023-08-28-at-6.37.32-PM.png" class="kg-image" alt loading="lazy" width="1572" height="272" srcset="https://jayeshkawli.com/content/images/size/w600/2023/08/Screenshot-2023-08-28-at-6.37.32-PM.png 600w, https://jayeshkawli.com/content/images/size/w1000/2023/08/Screenshot-2023-08-28-at-6.37.32-PM.png 1000w, https://jayeshkawli.com/content/images/2023/08/Screenshot-2023-08-28-at-6.37.32-PM.png 1572w" sizes="(min-width: 720px) 720px"></figure><p>As you can see all our tests passed with the expected result.</p><h3 id="summary">Summary</h3><p>So this was all about knowing property wrappers in Swift. They were first introduced in Swift 5.1 and have been gaining propularity since. They are an excellent way to centralize transoformation logic, avoid repetition and performing validation on any input as long as input conforms to property wrapper conditions and is decorated with annotation. </p><p>Are you using property wrapper in your application? Did you find it hard to use them for the first time? Do you find any paint points using them? If you were unfamiliar with it before, did you find this article useful to be able to create your own custom property wrappers? Let me know. I would love to know your thoughts and feedback about this topic.</p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep me writing more articles like this.</p><h3 id="consulting-services">Consulting Services</h3><p>I also provide a few consulting services on Topmate.io, and you can reach out to me there too. These services include,</p><ol><li><a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">Let&apos;s Connect</a></li><li><a href="https://topmate.io/jayesh_kawli/62010?ref=jayeshkawli.com">Resume Review</a></li><li><a href="https://topmate.io/jayesh_kawli/62009?ref=jayeshkawli.com">1:1 Mentorship</a></li><li><a href="https://topmate.io/jayesh_kawli/62012?ref=jayeshkawli.com">Interview Preparation &amp; Tips</a></li><li><a href="https://topmate.io/jayesh_kawli/63004?ref=jayeshkawli.com">Conference Speaking</a></li><li><a href="https://topmate.io/jayesh_kawli/215835?ref=jayeshkawli.com">Take-home Exercise Help (iOS)</a></li><li><a href="https://topmate.io/jayesh_kawli/62008?ref=jayeshkawli.com">Career Guidance</a></li><li><a href="https://topmate.io/jayesh_kawli/62011?ref=jayeshkawli.com">Mock Interview</a></li></ol>]]></content:encoded></item><item><title><![CDATA[How to Apply Global Styling in SwiftUI Apps]]></title><description><![CDATA[How to apply global style to SwiftUI apps. Use SwiftUI environment variables to apply global color, styles, fonts to the SwiftUI app with example]]></description><link>https://jayeshkawli.com/how-to-environment-variables-for-global-styling-in-swiftui-apps/</link><guid isPermaLink="false">64df3ed72b599204c7ba6af1</guid><category><![CDATA[Advanced Swift]]></category><category><![CDATA[Swift]]></category><category><![CDATA[Global change]]></category><category><![CDATA[Global Setting]]></category><category><![CDATA[global styling]]></category><category><![CDATA[environment variable]]></category><category><![CDATA[EnvironmentKey]]></category><category><![CDATA[EnvironmentValue]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Tue, 22 Aug 2023 09:02:15 GMT</pubDate><media:content url="https://jayeshkawli.com/content/images/2023/08/Screenshot-2023-08-22-at-11.19.40-AM.png" medium="image"/><content:encoded><![CDATA[<blockquote>Although not strictly required, it&apos;s good to have a basic understanding of creating custom Environment Keys in SwiftUI. If you haven&apos;t already read it, I strongly recommend you to go through my previous blog article about <a href="https://jayeshkawli.com/using-custom-environment-key-values-in-swiftui/">creating a custom environment key-value pair in SwiftUI</a> so that you will get more context from the post I am about to write</blockquote><h3 id="introduction">Introduction</h3><img src="https://jayeshkawli.com/content/images/2023/08/Screenshot-2023-08-22-at-11.19.40-AM.png" alt="How to Apply Global Styling in SwiftUI Apps"><p>In today&apos;s post, I am going to talk about how to apply the global style to the app with one click with the help of <code>EnvironmentKey</code> protocol. Often time we need to apply different styles to the app which may include fonts, colors, and icons that cater to specific users or app environments. Before <code>SwiftUI</code>, we had to maintain a global state and manually apply styles to the app. </p><p>Now with custom environment keys created using <code>EnvironmentKey</code> protocol, we can instantly change the whole app layout by updating styles associated with the current environment and automatically propagating that change to everywhere in the app that is using these styles to design the layout.</p><p>At the end of this tutorial, you will have a better understanding of,</p><ol><li>Creating a custom environment key</li><li>Creating and updating custom styles on the fly</li><li>Updating custom environment properties</li></ol><h3 id="creating-custom-environment-key">Creating Custom Environment Key</h3><p>In the first step, we will create a struct that represents a custom environment key. We will add required properties to it and update it on the fly as per the requirement - In this case, we will add a computed property called <code>styling</code> that represents the current app style. </p><p>First, we will create a <code>Styling</code> struct that represents app styles such as spacing, colors, icons, etc.</p><pre><code class="language-swift">
import Combine
import SwiftUI

struct Styling: Identifiable, Hashable {
    let id = UUID()

    var spacing: Spacings
    var colors: Colors
    var icons: Icons
    var fonts: Fonts
    var title: String

    init(spacing: Spacings = Spacings(), colors: Colors = Colors(), icons: Icons = Icons(), fonts: Fonts = Fonts()) {
        self.spacing = spacing
        self.colors = colors
        self.icons = icons
        self.fonts = fonts
        self.title = &quot;Marigold&quot;
    }
}

</code></pre><p>This struct represents a custom style applied from the app. We will add four core styles components as follow,</p><ol><li>Marigold</li><li>Sunflower</li><li>Brinjal</li><li>default style (Same as Marigold)</li></ol><pre><code class="language-swift">
extension Styling {
    static var `default` = Styling()

    static var marigold = Styling()

    static var sunflower: Styling = {
        var styling = Styling()
        styling.colors = Colors()
        styling.colors.background = .green
        styling.colors.subText = styling.colors.yellow
        styling.colors.text = .red
        styling.icons.homePageIconName = &quot;paperplane.fill&quot;
        styling.icons.stylePageIconName = &quot;tray.2&quot;
        styling.title = &quot;Sunflower&quot;
        return styling
    }()

    static var brinjal: Styling = {
        var styling = Styling()
        styling.colors = Colors()
        styling.colors.text = .purple
        styling.colors.subText = styling.colors.purple
        styling.colors.background = styling.colors.yellow
        styling.icons.homePageIconName = &quot;bookmark.fill&quot;
        styling.icons.stylePageIconName = &quot;person.circle&quot;
        styling.title = &quot;Brinjal&quot;
        return styling
    }()
}


</code></pre><p>Next, we will create a custom environment key conforming to <code>EnvironmentKey</code> protocol. To satisfy the protocol conformance, the struct needs to implement the following property,</p><p><code>static var defaultValue: Self.Value { get }</code></p><p>We want to use the default style when no style is assigned. So we will use the default <code>Style</code> initializer to create a style struct with default style values and assign it to <code>defaultValue</code> on the custom environment key.</p><pre><code class="language-swift">
struct StylingEnvironmentKey: EnvironmentKey {
    static var defaultValue: Styling = Styling()
}

</code></pre><p>We will add a computed property <code>styling</code> on the built-in type <code>EnvironmentValues</code> which can be written to and read from using getter and setter. This will be used to read and write custom styles to the newly created environment key.</p><pre><code class="language-swift">
extension EnvironmentValues {
    var styling: Styling {
        get { self[StylingEnvironmentKey.self] }
        set { self[StylingEnvironmentKey.self] = newValue }
    }
}

</code></pre><p>When you create a new view, you may need to assign custom styles to the current environment variable before presenting it to the user. If you don&apos;t specify anything, it will use the default styles. In order to facilitate it, we will add a utility function by creating an extension on the <code>View</code> type.</p><pre><code class="language-swift">
extension View {
    func setStyling(_ value: Styling) -&gt; some View {
        environment(\.styling, value)
    }
}


</code></pre><p>Using this extension, you can assign any styles you want to the custom view using the following one-liner,</p><pre><code class="language-swift">
HomeView()
    .setStyling(.brinjal)
    
    </code></pre><h3 id="creating-structs-for-custom-colors-spacings-fonts-and-icons">Creating Structs for custom Colors, Spacings, Fonts, and Icons</h3><p>Before we jump into applying styles, we need to create custom structs for our styles. These include the following components,</p><ol><li>Spacings</li><li>Colors</li><li>Fonts</li><li>Icons</li></ol><p>We will also add a couple of utilities to convert SwiftUI <code>Color</code> type into UIKit <code>UIColor</code> type</p><pre><code class="language-swift">
//Spacings.swift

import Foundation
import SwiftUI

struct Spacings: Hashable {

    static let _8: CGFloat = 8
    static let _12: CGFloat = 12
    static let _16: CGFloat = 16
    static let _20: CGFloat = 20

    init() {

    }

    var large: CGFloat {
        return Spacings._16
    }

    var medium: CGFloat {
        return Spacings._12
    }

    var small: CGFloat {
        return Spacings._8
    }

    var margin: CGFloat {
        return Spacings._20
    }
}


</code></pre><pre><code class="language-swift">
//Colors.swift

import SwiftUI

struct Colors: Hashable {

    var background = Color(hex: 0xffddff)
    var text = Color(hex: 0x181A1B)
    var subText = Color(hex: 0x94999E)
    var tintColor = Color.red

    var yellow = Color.yellow
    var purple = Color.purple

    var navyBlue = Color(hex: 0x3D73CD)
    lazy var primary = navyBlue

    init() {

    }
}

// Color + Utilities
extension Color {
    init(hex: UInt) {
        self.init(
            red: Double((hex &gt;&gt; 16) &amp; 0xff) / 255,
            green: Double((hex &gt;&gt; 08) &amp; 0xff) / 255,
            blue: Double((hex &gt;&gt; 00) &amp; 0xff) / 255,
            opacity: 1.0
        )
    }

    var uiKitColor: UIColor {
        UIColor(self)
    }
}


</code></pre><pre><code class="language-swift">
//Fonts.swift

import SwiftUI

struct Fonts: Hashable {

    var title: Font = Font.system(.headline)
    var bodyFont: Font = Font.system(.body)

    init() {

    }
}

</code></pre><pre><code class="language-swift">
//Icons.swift

import SwiftUI

struct Icons: Hashable, Identifiable {

    var homePageIconName = &quot;checkmark.circle.fill&quot;
    var stylePageIconName = &quot;smallcircle.fill.circle.fill&quot;

    func hash(into hasher: inout Hasher) {
        hasher.combine(homePageIconName)
        hasher.combine(stylePageIconName)
    }

    var id: String {
        homePageIconName + stylePageIconName
    }

    var homePageIcon: Image {
        Image(systemName: homePageIconName)
    }
    var stylePageIcon: Image {
        Image(systemName: stylePageIconName)
    }

    init() {

    }
}


</code></pre><h3 id="creating-a-custom-environment-object">Creating a Custom Environment Object</h3><p>Now that we have styles and custom environment key ready, let&apos;s start with the custom environment object with <code>styling</code> property that encodes all the appearance settings.</p><p>We will use the singleton instance of this class applicable to all views in the app.</p><pre><code class="language-swift">
import SwiftUI

final class PlantEnvironment {
    static var shared = PlantEnvironment()

	var styling: Styling

    init(styling: Styling = .marigold) {
        self.styling = styling
    }
}

</code></pre><p> In the above example, we are using the <code>shared</code> variable which represents the singleton of type <code>PlantEnvironment</code>. In case the app doesn&apos;t explicitly specify, we are using marigold as a default style.</p><p>We are expecting the whole app to change its appearance as soon as the style is changed. However, the above code only works on the SwiftUI views. Presumably, your app may also be making use of <code>UINavigationBar</code> APIs which will have no effect from the style change. </p><p>To apply styles to UIKit elements, we need to create an observer on <code>styling</code> property and update the navigation bar style as soon as a style change is detected.</p><p>The <code>PlantEnvironment</code> class looks like this after an update</p><pre><code class="language-swift">
import Combine
import SwiftUI

final class PlantEnvironment: ObservableObject {
    static var shared = PlantEnvironment()

    @Published var styling: Styling

    private var stylingCancellable: AnyCancellable?

    init(styling: Styling = .marigold) {
        self.styling = styling
        updateAppearances(with: styling)

        stylingCancellable = $styling.sink {
            self.updateAppearances(with: $0)
        }
    }

    private func updateAppearances(with styling: Styling) {
        let backgroundColor = styling.colors.background.uiKitColor
        let titleColor = styling.colors.text.uiKitColor
        let tintColor = styling.colors.tintColor.uiKitColor
        let titleFont = UIFont.boldSystemFont(ofSize: 25)
        let largeTitleFont = UIFont.systemFont(ofSize: 34)

        let navigationAppearance = UINavigationBarAppearance()
        navigationAppearance.configureWithOpaqueBackground()
        navigationAppearance.backgroundColor = backgroundColor

        navigationAppearance.titleTextAttributes = [.foregroundColor: titleColor, .font: titleFont]
        navigationAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor, .font: largeTitleFont]

        UINavigationBar.appearance().standardAppearance = navigationAppearance
        UINavigationBar.appearance().compactAppearance = navigationAppearance
        UINavigationBar.appearance().scrollEdgeAppearance = navigationAppearance

        UINavigationBar.appearance().tintColor = tintColor

        for window in UIApplication.shared.windows {
            for view in window.subviews {
                view.removeFromSuperview()
                window.addSubview(view)
            }
        }
    }
}


</code></pre><p>We will call <code>updateAppearances</code> the first time when the <code>PlantEnvironment</code> object is instantiated and then every time after <code>styling</code> is changed.</p><p>We also need a utility to assign the current style the <code>styling</code> key associated with the environment and by default pass the <code>PlantEnvironment</code> to SwiftUI views in the form of <code>EnvironmentObject</code>. </p><pre><code class="language-swift">
extension View {
    func plantEnvironment(_ plantEnvironment: PlantEnvironment = .shared) -&gt; some View {
        self
            .environment(\.styling, plantEnvironment.styling)
            .environmentObject(plantEnvironment)
    }
}

</code></pre><p>Using this utility, you can apply any style and pass the pre-defined <code>PlantEnvironment</code> object to view before it is presented to the user. That way, the view will style itself using the style property defined in the current plant environment object.</p><p>This utility can be used on any view using the following syntax. By default, we will use the shared singleton instance of <code>PlantEnvironment</code></p><pre><code class="language-swift">
HomeView()
    .plantEnvironment(.shared)
    
    </code></pre><h3 id="building-an-app-with-custom-styles-and-environments">Building an app with Custom Styles and Environments</h3><p>Now that our foundation is ready, we will create an app that will make use of custom styles and a global environment that will globally apply styles to all parts of the app as long as that screen is using the styles associated with the custom <code>PlantEnvironment</code> object in the form of <code>EnvironmentObject</code>.</p><p>First, we will create a top-level view that will use the <code>shared</code> singleton object of type <code>PlantEnvironment</code> and implicitly pass it down to all its children. </p><pre><code class="language-swift">
import SwiftUI

@main
struct GlobalStylingApp: App {

    @StateObject var plantEnvironment: PlantEnvironment = .shared

    var body: some Scene {
        WindowGroup {
            NavigationStack {
                HomeView()
            }
            .plantEnvironment(plantEnvironment)
            .navigationBarTitleDisplayMode(.inline)
            .background(plantEnvironment.styling.colors.background)
        }
    }
}


</code></pre><p>Children can reference, access, and use the styles associated with this passed object by referring to it with the following line,</p><p><code>@EnvironmentObject var plantEnvironment: PlantEnvironment</code> </p><p>on the top level of the file. </p><p>As long as the <code>plantEnvironment</code> object is passed to the view, you can access the styles and apply color, spacing, and images associated with it to the corresponding view. </p><pre><code class="language-swift">
import SwiftUI

struct HomeView: View {

    @EnvironmentObject var plantEnvironment: PlantEnvironment

    var body: some View {
        VStack {
            plantEnvironment.styling.icons.homePageIcon

            NavigationLink {
                
            } label: {
                Text(&quot;Customize Styles&quot;)
                    .foregroundStyle(plantEnvironment.styling.colors.text)
                    .padding()
            }
        }
        .navigationTitle(plantEnvironment.styling.title)
    }
}

</code></pre><p>If you run the app now, you will see the default marigold style applied to the home screen.</p><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/08/Simulator-Screenshot---iPhone-14-Pro---2023-08-22-at-10.42.47.png" class="kg-image" alt="How to Apply Global Styling in SwiftUI Apps" loading="lazy" width="295" height="640"></figure><h3 id="changing-styles">Changing Styles</h3><p>So far we saw how to create custom environment keys and styles that can be applied throughout the app. In this section, we will see how to update app styles on the fly and verify those changes.</p><p>We will use the <code>SegmentedPicker</code> to allow users to choose any style they want. As soon as the style choice is made, we will assign the chosen value to the <code>styling</code> property on <code>plantEnvironment</code> environment object passed to this view.</p><pre><code class="language-swift">
@EnvironmentObject var plantEnvironment: PlantEnvironment

Picker(&quot;Picker&quot;, selection: $plantEnvironment.styling) {
    Text(&quot;Marigold&quot;)
        .tag(Styling.marigold)

    Text(&quot;Sunflower&quot;)
        .tag(Styling.sunflower)

    Text(&quot;Brinjal&quot;)
        .tag(Styling.brinjal)
}
.pickerStyle(SegmentedPickerStyle())


</code></pre><p>We are assigning the tag to each of the segmented picker choices. As the user makes the choice, it will take the chosen value and automatically assign it to <code>styling</code> property on the <code>plantEnvironment</code> object.</p><p>With this main portion ready, let&apos;s create a style view from where user can change styles and add navigation to it from the home screen,</p><pre><code class="language-swift">
//StyleView.swift

import SwiftUI

struct StyleView: View {

    @EnvironmentObject var plantEnvironment: PlantEnvironment

    var body: some View {
        VStack {
            Text(&quot;Customize Styles&quot;)

            plantEnvironment.styling.icons.stylePageIcon

            Picker(&quot;Picker&quot;, selection: $plantEnvironment.styling) {
                Text(&quot;Marigold&quot;)
                    .tag(Styling.marigold)

                Text(&quot;Sunflower&quot;)
                    .tag(Styling.sunflower)

                Text(&quot;Brinjal&quot;)
                    .tag(Styling.brinjal)
            }
            .pickerStyle(SegmentedPickerStyle())
            }

        }
        .padding()
        .navigationTitle(plantEnvironment.styling.title)
    }
}

</code></pre><p>Navigating from the Home screen</p><pre><code class="language-swift">
//HomeView.swift

NavigationLink {
    StyleView()
} label: {
    Text(&quot;Customize Styles&quot;)
        .foregroundStyle(plantEnvironment.styling.colors.text)
        .padding()
}

</code></pre><p>Now let&apos;s run the app and observe styles changes as the user toggles between available choices,</p><figure class="kg-card kg-video-card"><div class="kg-video-container"><video src="https://jayeshkawli.com/content/media/2023/08/Demo.mp4" poster="https://img.spacergif.org/v1/566x1228/0a/spacer.png" width="566" height="1228" playsinline preload="metadata" style="background: transparent url(&apos;https://jayeshkawli.com/content/images/2023/08/media-thumbnail-ember169.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div></figure><h3 id="verifying-styles-changes">Verifying Styles Changes</h3><p>To make sure our style changes get propagated everywhere in the app, we will add a navigation link to <code>StyleView</code> screen which will take us further to the styles demo screen where we will be able to see all the changes associated with the current ly applied style.</p><p><strong>Navigation from <code>StyleView.swift</code> screen</strong></p><pre><code class="language-swift">
......
...

Picker(&quot;Picker&quot;, selection: $plantEnvironment.styling) {
    Text(&quot;Marigold&quot;)
        .tag(Styling.marigold)

    Text(&quot;Sunflower&quot;)
        .tag(Styling.sunflower)

    Text(&quot;Brinjal&quot;)
        .tag(Styling.brinjal)
}
.pickerStyle(SegmentedPickerStyle())

NavigationLink {
    StyleUpdateDemoView()
} label: {
    Text(&quot;Tap to view updated styles&quot;)
        .foregroundStyle(plantEnvironment.styling.colors.text)
}

......
....
..


</code></pre><p><strong><code>StyleUpdateDemoView.swift</code> Screen</strong></p><pre><code class="language-swift">
import SwiftUI

struct StyleUpdateDemoView: View {

    @EnvironmentObject var plantEnvironment: PlantEnvironment

    var body: some View {
        VStack {
            Text(&quot;Title&quot;)
                .foregroundStyle(plantEnvironment.styling.colors.text)
            Text(&quot;Subtitle&quot;)
                .foregroundStyle(plantEnvironment.styling.colors.subText)
        }
        .background(plantEnvironment.styling.colors.background)
        .navigationTitle(plantEnvironment.styling.title)
    }
}

</code></pre><p>Now let&apos;s run the app, change styles, and view style changes on all three screens.</p><figure class="kg-card kg-video-card"><div class="kg-video-container"><video src="https://jayeshkawli.com/content/media/2023/08/Demo-1.mp4" poster="https://img.spacergif.org/v1/566x1228/0a/spacer.png" width="566" height="1228" playsinline preload="metadata" style="background: transparent url(&apos;https://jayeshkawli.com/content/images/2023/08/media-thumbnail-ember213.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div></figure><h3 id="source-code">Source Code</h3><p>The full source code for this tutorial is available on the <a href="https://github.com/jayesh15111988/GlobalStyling?ref=jayeshkawli.com">GitHub GlobalStyles Repo</a> for further reference</p><h3 id="summary">Summary</h3><p>So this was all about applying global styles in the iOS app using SwiftUI APIs. Hope you liked it and will be helpful for you to improve styles in your existing app. Have you used any other global styling framework? Do you have any suggestions for improvement in the current code or this blog post? Let me know. I would love to hear your thoughts on this topic.</p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep me writing more articles like this.</p><h3 id="consulting-services">Consulting Services</h3><p>I also provide a few consulting services on Topmate.io, and you can reach out to me there too. These services include,</p><ol><li><a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">Let&apos;s Connect</a></li><li><a href="https://topmate.io/jayesh_kawli/62010?ref=jayeshkawli.com">Resume Review</a></li><li><a href="https://topmate.io/jayesh_kawli/62009?ref=jayeshkawli.com">1:1 Mentorship</a></li><li><a href="https://topmate.io/jayesh_kawli/62012?ref=jayeshkawli.com">Interview Preparation &amp; Tips</a></li><li><a href="https://topmate.io/jayesh_kawli/63004?ref=jayeshkawli.com">Conference Speaking</a></li><li><a href="https://topmate.io/jayesh_kawli/215835?ref=jayeshkawli.com">Take-home Exercise Help (iOS)</a></li><li><a href="https://topmate.io/jayesh_kawli/62008?ref=jayeshkawli.com">Career Guidance</a></li><li><a href="https://topmate.io/jayesh_kawli/62011?ref=jayeshkawli.com">Mock Interview</a></li></ol>]]></content:encoded></item><item><title><![CDATA[How to Create a top and bottom Sticky View in SwiftUI]]></title><description><![CDATA[How to create a sticky view in SwiftUI with code and examples in Swift. Create a SwiftUI sticky view for scrolling with scroll view and VStack]]></description><link>https://jayeshkawli.com/how-to-create-a-sticky-view-with-swiftui/</link><guid isPermaLink="false">64de33612b599204c7ba69c0</guid><category><![CDATA[Advanced SwiftUI]]></category><category><![CDATA[New SwiftUI APIs]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[Sticky View]]></category><category><![CDATA[sticky]]></category><category><![CDATA[Vertical Scroll view]]></category><category><![CDATA[Scroll view]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Fri, 18 Aug 2023 09:00:58 GMT</pubDate><content:encoded><![CDATA[<p>SwiftUI offers a flexible way to create views on iOS. In today&apos;s blog post, we are going to look at how to create a sticky view accompanied by a scrolling counterpart on iOS using SwiftUI APIs.</p><p>A sticky view is any view that sticks either at the very top or bottom and allows the view below or above it to scroll respectively. </p><h3 id="applications-of-sticky-view">Applications of Sticky View</h3><p>The sticky view can be used in cases where you want users to scroll through the long content, but are still able to fix the button or content that the user always sees. For example, the booking button, checkout button, or the top promotional banner.</p><h3 id="creating-a-sticky-view">Creating a Sticky View</h3><p>To create a sticky view, we need two components</p><ol><li>Scroll view </li><li>Sticky view attached to scroll view</li></ol><p>But we cannot put everything in the scroll view, so we need to embed a vertical stack inside it to be able to stack heterogenous views together.</p><p>We also need to use an external vertical stack view which will embed a scroll view and then a sticky view at the bottom (Or top depending on our use case)</p><p>With this understanding, our view hierarchy will look something like this with a sticky view at the bottom,</p><p>VStack<br> &#xA0; &#xA0;- ScrollView<br> &#xA0; &#xA0; &#xA0; &#xA0;- VStack<br>&#x2003;&#x2003;&#x2003;-View<br>&#x2003;&#x2003;&#x2003;-View<br>&#x2003;&#x2003;&#x2003;....<br>&#x2003;&#x2003;&#x2003;..<br>&#x2003;- Sticky View</p><p>Alternatively, if you want to stick the view at the top, you can tweak the hierarchy like this,</p><p>VStack<br> &#xA0; &#xA0;- Sticky View<br>&#x2003;- ScrollView<br>&#x2003;&#x2003;- VStack<br>&#x2003;&#x2003;&#x2003;-View<br>&#x2003;&#x2003;&#x2003;-View<br>&#x2003;&#x2003;&#x2003;....<br>&#x2003;&#x2003;&#x2003;..</p><h3 id="implementation">Implementation</h3><p>Now that we know the hierarchy, let&apos;s start implementing it in SwiftUI. We will show the random items in the list embedded inside the scroll view.</p><p>In order to get a better understanding of how items are lined up, we will color them with a suitable background color.</p><pre><code class="language-swift">
import SwiftUI

struct StickyView: View {

    private let items = (1...50).map { &quot;Item \($0)&quot; }

    var body: some View {
        VStack(spacing: 0) {
            ScrollView(showsIndicators: false) {
                VStack(spacing: 0) {
                    ForEach(items, id: \.self) { itemName in
                        Text(itemName)
                    }
                }
                .background(.red)
            }
            Button {

            } label: {
                Text(&quot;Add to Cart&quot;)
            }
            .background(.yellow)
        }
    }
}

</code></pre><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/08/Simulator-Screenshot---iPhone-14---2023-08-18-at-09.40.41-2.png" class="kg-image" alt loading="lazy" width="370" height="800"></figure><p>As you can see views are properly laid out, but they do not utilize the full width which makes the UI look awkward. We will set views&apos; <code>maxWidth</code> to infinity to fully stretch to the available width. We will apply this modifier to internal <code>VStack</code> and sticky views.</p><p>We will also add default padding around the sticky view for better UI. With these final changes, our UI will look like this.</p><blockquote>I am not going into details about how to stick the view on the top of the scroll view. Please refer to the view hierarchy above to change the layout and move the sticky view to the top.</blockquote><p></p><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/08/Simulator-Screenshot---iPhone-14---2023-08-18-at-09.46.20.png" class="kg-image" alt loading="lazy" width="370" height="800"></figure><h3 id="adding-navigation-bar">Adding Navigation Bar</h3><p>To make it look even better, we will embed it inside the <code>NavigationView</code> with a title and inline display.</p><pre><code class="language-swift">
var body: some View {
    NavigationStack {
        VStack(spacing: 0) {
            VStack(spacing: 0) {
                ScrollView(showsIndicators: false) {
                    VStack(spacing: 0) {
                        ForEach(items, id: \.self) { itemName in
                            Text(itemName)
                        }
                    }
                    .frame(maxWidth: .infinity)
                    .background(.red)
                }
                Button {

                } label: {
                    Text(&quot;Add to Cart&quot;)
                }
                .padding()
                .frame(maxWidth: .infinity)
                .background(.yellow)
            }
        }
        .navigationTitle(&quot;Sticky View&quot;)
        .navigationBarTitleDisplayMode(.inline)
    }
}

</code></pre><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/08/Simulator-Screenshot---iPhone-14---2023-08-18-at-10.00.15.png" class="kg-image" alt loading="lazy" width="296" height="640"></figure><blockquote>If you simply wanted to know how to create a sticky top / bottom view in SwiftUI, you can stop here. If you want to know how to create a generic sticky view that can embed any sticky or scrollable content, please read on.</blockquote><h3 id="creating-a-generic-sticky-component-with-scrollable-content">Creating a Generic Sticky Component with Scrollable Content</h3><p>What we did so was good, but not good enough. This was specific to one case, but what if you want to add custom scrollable and sticky content? We will build the generic scrollable + sticky content view which can accommodate any content type.</p><p>Our Generic component will take the following parameters which can be customized during initialization.</p><ol><li>Title</li><li>Scrollable content</li><li>Sticky content</li><li>Stick position (Top or bottom)</li></ol><p>Now, let&apos;s refactor our original code to use these customized parameters and get rid of lines that we no longer need, such as background colors used for debugging.</p><blockquote>We will use <code>@ViewBuilder</code> annotation to pass custom views in the component initializer</blockquote><pre><code class="language-swift">
import SwiftUI

struct ScrollableViewWithStickyComponentView&lt;ScrollableContent: View, StickyView: View&gt;: View {

    enum StickPosition {
        case top
        case bottom
    }

    private let title: String
    private let scrollableContent: ScrollableContent
    private let stickyView: StickyView
    private let stickPosition: StickPosition

    init(title: String, @ViewBuilder scrollableContent: () -&gt; ScrollableContent, @ViewBuilder stickyView: () -&gt; StickyView, stickPosition: StickPosition) {
        self.title = title
        self.scrollableContent = scrollableContent()
        self.stickyView = stickyView()
        self.stickPosition = stickPosition
    }

    var body: some View {
        NavigationStack {
            VStack(spacing: 0) {
                VStack(spacing: 0) {
                    if stickPosition == .top {
                        stickyView
                            .frame(maxWidth: .infinity)
                    }
                    ScrollView(showsIndicators: false) {
                        VStack(spacing: 0) {
                            scrollableContent
                        }
                        .frame(maxWidth: .infinity)
                    }
                    if stickPosition == .bottom {
                        stickyView
                            .frame(maxWidth: .infinity)
                    }
                }
            }
            .navigationTitle(title)
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

</code></pre><p>Now that our generic version is ready, let&apos;s start spinning out some views with scrollable content and a sticky view on the top and the bottom.</p><pre><code class="language-swift">

let items = (1...50).map { &quot;Item \($0)&quot; }

var body: some View {
    ScrollableViewWithStickyComponentView(title: &quot;Sticky View on Top&quot;, scrollableContent: {
        ForEach(items, id: \.self) { itemName in
            Text(itemName)
        }
    }, stickyView: {
        Button(action: {

        }, label: {
            Text(&quot;Checkout&quot;)
        })
    }, stickPosition: .top)
}

</code></pre><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/08/Simulator-Screenshot---iPhone-14---2023-08-18-at-10.49.21.png" class="kg-image" alt loading="lazy" width="296" height="640"></figure><pre><code class="language-swift">

let items = (1...50).map { &quot;Item \($0)&quot; }

var body: some View {
    ScrollableViewWithStickyComponentView(title: &quot;Sticky View on Bottom&quot;, scrollableContent: {
        ForEach(items, id: \.self) { itemName in
            Text(itemName)
        }
    }, stickyView: {
        Button(action: {

        }, label: {
            Text(&quot;Checkout&quot;)
        })
        .padding()
    }, stickPosition: .bottom)
}

</code></pre><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/08/Simulator-Screenshot---iPhone-14---2023-08-18-at-10.51.27.png" class="kg-image" alt loading="lazy" width="296" height="640"></figure><hr><h3 id="summary">Summary</h3><p>So this is all for today. I hope this article was useful to you for understanding sticky view and its combination with scrollable content to build generic views in SwiftUI. Have you encountered similar situations in your app? Do you have any idea about extending the current implementation? Do you see any issues and want to build improvements on top of the existing code? Please let me know. I would love to hear your thoughts and suggestions.</p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep me writing more articles like this.</p><h3 id="consulting-services">Consulting Services</h3><p>I also provide a few consulting services on Topmate.io, and you can reach out to me there too. These services include,</p><ol><li><a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">Let&apos;s Connect</a></li><li><a href="https://topmate.io/jayesh_kawli/62010?ref=jayeshkawli.com">Resume Review</a></li><li><a href="https://topmate.io/jayesh_kawli/62009?ref=jayeshkawli.com">1:1 Mentorship</a></li><li><a href="https://topmate.io/jayesh_kawli/62012?ref=jayeshkawli.com">Interview Preparation &amp; Tips</a></li><li><a href="https://topmate.io/jayesh_kawli/63004?ref=jayeshkawli.com">Conference Speaking</a></li><li><a href="https://topmate.io/jayesh_kawli/215835?ref=jayeshkawli.com">Take-home Exercise Help (iOS)</a></li><li><a href="https://topmate.io/jayesh_kawli/62008?ref=jayeshkawli.com">Career Guidance</a></li><li><a href="https://topmate.io/jayesh_kawli/62011?ref=jayeshkawli.com">Mock Interview</a></li></ol>]]></content:encoded></item><item><title><![CDATA[How to Become a Better Engineering Manager]]></title><description><![CDATA[Traits of and how to become a good engineering manager. Management tips. How to manage people and grow. Software engineering management career]]></description><link>https://jayeshkawli.com/how-to-become-a-better-engineering-manager/</link><guid isPermaLink="false">64dde3302b599204c7ba681b</guid><category><![CDATA[Management Lessons]]></category><category><![CDATA[engineering manager]]></category><category><![CDATA[Management]]></category><category><![CDATA[Project Management]]></category><category><![CDATA[better]]></category><category><![CDATA[Lessons]]></category><category><![CDATA[Life lessons]]></category><category><![CDATA[manager]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Thu, 17 Aug 2023 14:43:28 GMT</pubDate><content:encoded><![CDATA[<p>An engineering manager is a key role in the organization and employees&apos; lives. It&apos;s difficult to be a good software engineer, but it&apos;s much harder to be an engineering manager. As a manager, your expertise is not limited to management issues, but you also need to have a strong engineering background to tackle any technical problems and support your team in the hour of crisis.</p><p>I have worked with many types of people - good engineering managers, bad engineering managers, people who had no title of manager but still performed their duties as if they were EMs, and those who had no idea what engineering management is despite being in the role for many years.</p><blockquote>The view expressed in this post are completely my own and are based on my personal experiences. They are by no means the global representation of engineering manager traits</blockquote><p>When I am employed by the company, there are some things I expect my EM (Engineering manager) to do in order to support the team and help people around them grow. In this post, I will talk about things they should or should not do to become better engineering manager who is respected by their direct reports, liked by their colleagues, considered competent by the management, foster a positive culture, and help the company make good progress.</p><h3 id="support-onboarding">Support Onboarding</h3><p>When a new person joins the team, EMs have to make sure they have good onboarding support to understand the underlying tech stack, sister teams, and domain knowledge. Provide them with all the resources and people they need to succeed at their job. Given your broad scope with the team, you don&apos;t have to handle everything. To make things simple and help other people on the team grow, you can also assign an experienced person from the team to become their mentor for the first few weeks on the new team. </p><p>Onboarding is not a fire-and-forget activity. Keep checking with them about how onboarding is going, whether are they facing any blockers, and honest feedback about the mentor. As you go along, help them take on a good mix of comfortable and challenging tasks that will demonstrate if they are learning and getting used to the team</p><h3 id="dont-micromanage">Don&apos;t Micromanage</h3><p>A lot of EMs think they are helping their direct reports by micromanaging their activities. Your job is to support them in their role, help them understand team culture and technology, and connect them with the right people and teams.</p><p>You can ask them what is expected, but you don&apos;t need to tell them how to do it. This is assuming they&apos;ve already gone through the required training and have ongoing feedback about their work. </p><p>When you micromanage people too much, they depend on you for everything they do. But if you give them leeway to apply their own understanding toward the problem, they will become more autonomous and independent in their thinking.</p><h3 id="share-feedback">Share Feedback</h3><p>The only way to positive improvement is to share and act on feedback. As an engineering manager, you should not hesitate to share critical feedback if you see gaps in employee performance. Don&apos;t criticize them while delivering the feedback, but let them know what caused it and ways to improve on it. Also, do regular check-ins to see how they are doing with the previous feedback and follow-up action.</p><p>Delivering critical feedback can result in an awkward situation, but there is no way around it. If you hold it back and only keep talking about good things, their performance will never be at the expected level and they won&apos;t get the opportunity to grow and rectify mistakes.</p><h3 id="be-open-to-feedback-from-direct-reports">Be Open to Feedback from Direct Reports</h3><p>One thing that most of the EMs think is, they are exempt from the feedback about their performance from direct reports. If you&apos;re working in a well-functioning corporation, companies offer all the employees to share their feedback about their managers.</p><p>Be transparent and open to receive feedback from your reporting team. Just as they do, you should be able to take critical feedback in a graceful manner. If you hear something that is not pleasant, don&apos;t be indignant or defensive. Try to think if it&apos;s valid feedback. If you think it&apos;s an unfair assessment of your behavior and treatment, ask them if they can come up with past examples.</p><p>Even if you&apos;re not happy with the feedback from your direct reports, don&apos;t get all vindictive. You might be violating company policy on harassment, which may destroy the team morale and transparency and they may completely stop providing the valuable feedback that you might have used for your own career development</p><h3 id="stand-for-them">Stand for Them</h3><p>As an engineering manager, you&apos;re representing your direct reports for appraisal, promotion, and due credits. If they are going through a difficult situation, support them and do everything within company policy to help them handle work-life balance. </p><p>If you think they have been given unfair treatment, stand with them and resolve the situation. If you think they deserve a promotion, work with them to create a strong case for promotion and push for it during the calibration meeting with strong facts about their contribution and impact on the company.</p><h3 id="be-honest-about-goals-and-promotions">Be Honest about Goals and Promotions</h3><p>It&apos;s no secret that everyone wants to progress in their career and wants more money. However, companies have rules about how one can be eligible for promotion. Work with employees to know which track they are interested in (Tech vs. management), set up goals that satisfy the bare minimum for promotion, and help them identify tasks that will make their promotion case even stronger.</p><p>Also, organize a monthly check-in meeting to see how they are performing and if they need any help with goals. Provide critical feedback, but also give them work of encouragement if they&apos;ve done an excellent job. </p><p>If they aren&apos;t ready for promotion yet, tell them why and what they need to do to be on the right track for promotion. Don&apos;t give them false promises if you cannot make it. You&apos;re just delaying the bad news and when the time comes for you to deliver it, it will be more awkward, or even worse, the employee may already have chosen to part ways with you.</p><h3 id="help-them-grow">Help them Grow</h3><p>Your job as an EM is to extend the employees&apos; network, take them out of their comfort zone, and encourage them to take on challenging work all the same while assuring them that you will be there to support them if they face any major hurdle.</p><p>The list of initiatives that can help them grow includes but not limited are, to mentoring, interviewing external candidates, leading a sprint planning meeting, hosting a SEV review, working with sister teams for a few weeks, or launching an end-to-end feature by coordinating with major stakeholders.</p><p>The good candidates for initiative could be something that contributes to the company and something an employee hasn&apos;t had experience with before.</p><h3 id="summary">Summary</h3><p>So this is all I have for today on how to become a better engineering manager. The management field could be daunting in the beginning due to people management, meetings, spreadsheets, and performance reviews, all while writing code in your free time. But it&apos;s also fun. You get a chance to help people grow and develop their careers, listen to their feedback, and share your experience so that they can learn from your mistakes.</p><p>The engineering manager is also an opportunity for you to show up your skills not just as a competent engineer but also as a good manager who knows how to balance priorities, grow the team, share feedback, and improve productivity.</p><p>Do you have tips of your own on the topic? I would love to hear! You can connect and message me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><h3 id="software-engineering-consultation">Software Engineering Consultation</h3><p>I also do consultations on <a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">Topmate.io</a> regarding carer guidance, interview preparation, mentorship, and take-home exercises. If you are interested, feel free to <a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">book a free 1:1 session</a> to know more about me and my services. I will be more than happy to answer any questions you have about it. </p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep me writing more articles like this.</p>]]></content:encoded></item><item><title><![CDATA[How to Cope with Layoffs and Job Loss?]]></title><description><![CDATA[How to deal with layoffs, rejections and job loss. Dealing with anxiety and job loss. Find a new job in a wave of layoffs. Coping with stress]]></description><link>https://jayeshkawli.com/how-to-cope-up-with-layoffs/</link><guid isPermaLink="false">64dca0862b599204c7ba66c2</guid><category><![CDATA[stress]]></category><category><![CDATA[job loss]]></category><category><![CDATA[layoffs]]></category><category><![CDATA[opportunity]]></category><category><![CDATA[perspective]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Wed, 16 Aug 2023 13:19:09 GMT</pubDate><content:encoded><![CDATA[<p>Job losses are never easy. No matter how much you justify your feelings by saying you are not your job, it still feels personal when someone calls you redundant, unnecessary, or whatever other adjectives and wants to get rid of it.</p><p>I have been on both ends of receiving and giving unfortunate news of job loss. If you are going through this heartbreaking situation, I have some tips from my past experience to share that will help you cope and survive this stressful time</p><h3 id="dont-take-it-personally">Don&apos;t take it Personally</h3><p>I know it&apos;s easier said than done, but do not ever take layoffs personally. You were good at your job, you were looking for the best interest of your employer, and you were planning to stay with them for as long as you could, but things didn&apos;t work out on their end, and now they&apos;ve taken an unfortunate decision to lay you off.</p><p>Always remember that it&apos;s not a reflection of your commitment and capabilities. Some external circumstances might have forced companies to take this drastic action. That doesn&apos;t mean you are not capable of performing job duties or finding a better opportunity. </p><p>You are still You. As long as you have pre-requisite skills, experience, drive, and commitment, you can still find many opportunities that are suitable (Or even better) for you to grow in your career and get handsomely paid.</p><h3 id="take-a-break">Take a Break</h3><p>Although the first instinct after job loss is to find a new job as soon as possible, I would advise you to take some time off to think about the situation and calm your mind before going full throttle on the job search. Take a break to clear your mind, analyze things in retrospect, and think about what kind of team, industry, and employer you would like to work with.</p><p>If you don&apos;t have immediate financial urgency, you can also utilize this time to do things you always wanted to do but never got time to take action. If you want to be more adventurous, you can also explore consulting opportunities and even start your own business.</p><h3 id="take-a-clue-before-layoffs">Take a Clue Before Layoffs</h3><p>Nothing is worse than the immediate shock of losing a job when one minute you had a job and the next, you lose it. You can minimize the pain by looking around and getting telltale signs that job cuts are coming and soon start looking for other opportunities. So even when the time comes for a layoff, you can be prepared to recover from it and find the next opportunity quickly.</p><p>Telltale signs might include stricter performance reviews, the sudden disappearance of your teammates, companies focusing more on re-org and cost-cutting and anonymous discussions on Blind</p><h3 id="consult-a-lawyer">Consult a Lawyer</h3><p>When companies initiate layoffs, all the severance packages and so-called support aren&apos;t there for your sake. It is to protect companies from potential lawsuits. As long as companies are legally protected, they don&apos;t care about the size of severance packages. </p><p>Still, in these situations, employees might get short-changed as long as employment laws and post-employment compensation are concerned. As soon as you receive a notice, don&apos;t agree to or sign anything without consulting lawyers.</p><p>Lawyers are expensive, but their advice is still worth it. Some countries even require employers to cover the cost of legal representation for employees, so make sure to explore it before coming to a final decision.</p><h3 id="dont-let-the-past-affect-the-future">Don&apos;t let the Past Affect the Future</h3><p>One of the cons of job loss is, your start blaming yourself and doubting your own skills and ability to perform the job duties. This is totally untrue. You are not your job and given you possess enough skills and experience, you can still start fresh.</p><p>While doing a job search, focus on positive things and your impact from the past. Think about people you coached and mentored and helped them grow. Think about those challenging features you helped launch and that critical bug fix that saved the company from losing several thousand dollars and prospective customers. </p><p>Dwelling in the negative past won&apos;t take you anywhere. Instead, focus on the positives and be hopeful about the future.</p><h3 id="talk-to-someone">Talk to Someone</h3><p>In unfortunate circumstances like this, you might get overwhelmed with the myriad of thoughts on your mind. Don&apos;t feel ashamed about expressing your feeling, thoughts or even venting your frustration.</p><p>Talk to your colleagues who may have more context on your situation or talk to your spouse expressing your emotions. If you don&apos;t have anyone around to talk to, consider consulting a psychiatrist. Be it anyone, but talk. Don&apos;t let your thoughts stay suppressed in your mind.</p><h3 id="control-your-emotions">Control your Emotions</h3><p>It is natural to feel frustration and anger towards an entity that took your job away from you. Be it your manager, employer, colleague, or even a random person on the metro. But don&apos;t let your emotions run amok. You might end up doing something crazy that is impossible to undo or may hurt you permanently. </p><p>Don&apos;t vent on social media blaming your company or individuals who you might blame for your situation. When I first heard the news of my layoff, my first thought was to go on a shopping spree and spend everything I had in my savings account. But after calming down after a few moments, I realized how crazy that thought was.</p><p>If you feel like doing something like that, please refer to my previous point of &quot;Talking to Someone&quot; before taking any radical decision.</p><h3 id="summary">Summary</h3><p>Layoffs are hard. It&apos;s ok to be vulnerable during that phase to express your emotions and feelings. But also remember that dwelling on past negative things will land you nowhere. Instead, focus on your positive accomplishments, take a break, and surround yourself with people who you can talk to.</p><p>I also do consultations on <a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">Topmate.io</a> regarding carer guidance, interview preparation, mentorship, and take-home exercises. If you are interested, feel free to <a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">book a free 1:1 session</a> to know more about me and my services. I will be more than happy to answer any questions you have about it. </p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep me writing more articles like this.</p>]]></content:encoded></item><item><title><![CDATA[Moving to Netherlands from the USA]]></title><description><![CDATA[How I moved to Europe from USA. How to move and find job in Europe. Netherlands jobs. Work life balance in Netherlands. Good salary. topmate]]></description><link>https://jayeshkawli.com/moving-to-netherlands-from-usa/</link><guid isPermaLink="false">64dc8e1c2b599204c7ba6621</guid><category><![CDATA[relocation]]></category><category><![CDATA[Immigration]]></category><category><![CDATA[Germany]]></category><category><![CDATA[Netherlands]]></category><category><![CDATA[Europe]]></category><category><![CDATA[Work life balance]]></category><category><![CDATA[Job]]></category><category><![CDATA[Career]]></category><category><![CDATA[career change]]></category><category><![CDATA[Topmate]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Wed, 16 Aug 2023 10:08:53 GMT</pubDate><content:encoded><![CDATA[<h3 id="introduction">Introduction</h3><p>After living in the USA for more than a decade, last year me and my wife, we decided to move to the Netherlands. It was a sentimental decision since I spent more than a decade in the country and had to move away. But it was also a practical decision for both of us and our future.</p><h3 id="is-the-usa-the-best-country">Is the USA the Best Country?</h3><p>The USA, although touted as the Best country in the world by some, it&apos;s far from the fact. Yes, it does pay you handsomely, but its immigration system is completely broken. </p><p>Despite paying thousands of dollars in taxes over the years, our path to citizenship was never available, nor we could avail all the benefits that we deserved to rip off after paying a handsome sum to Uncle Sam. </p><p>After much deliberation, we decided it was not the right place for us, and we decided to look elsewhere.</p><h3 id="the-netherlands-was-one-of-our-numerous-choices-to-relocate">The Netherlands was one of our numerous choices to relocate</h3><p>We didn&apos;t think we would move to the Netherlands in the beginning. We wanted to get out of the USA and move somewhere where both primary applicants and their dependents could live and work with dignity, would not have restrictions on their jobs, people are open to migrants - especially those of color, are liberal, would be a great place to raise family and path to citizenship is feasible and well-defined.</p><p>Given this criteria, we shortlisted a few candidates that included U.K., Germany, Ireland, France, Canada, and if nothing else works, our home country - India. </p><h3 id="applying-for-jobs">Applying for Jobs</h3><p>We had 3 months to apply to these countries and finalize offer letters. Each country had its own rules for job application and immigration, so we had to take it into consideration as well. Since we were in the US EST time zone, there were times when we either had to wake up at midnight or early morning to appear for interviews.</p><p>We didn&apos;t polish our resumes for each job we were applying. We created one standard format, had cover letters if mandatory, and a list of things we expected from our next jobs.</p><p>Given the pay scale in the USA, there was no country that was offering as much as they paid in States, but European countries also offered benefits in terms of tax discounts (e.g. Netherlands), better lifestyle, free public transportation, pension benefit, and child education. So the low salary level was equally or more than compensated by better life in Europe and we were happy to accept it.</p><p>We both received offers from Norway, Germany, Netherlands, and Ireland, so it was time to make a final decision.</p><h3 id="finalizing-offers">Finalizing Offers</h3><p>Fortunately, my wife first received her official and most favorable offer from a Netherlands-based company. It was a great pay, benefits, and working culture. Since it checked everything from our list, we decided to accept it. After that, I solely focused on Netherlands-based companies. In the meantime, I got an offer from a German-based firm, but since they didn&apos;t allow remote work, I had to decline it. </p><p>After continuing the process for a few more weeks, finally I also had an offer from a Netherlands-based company, called TripActions (Now Navan).</p><h3 id="immigration-and-relocation-assistance">Immigration and Relocation Assistance</h3><p>My wife&apos;s employer offered immigration support and a generous relocation package. My firm offered a lump sum relocation payment and optionally immigration support. Thanks to this, we were able to live in a furnished apartment for 2 months when the Amsterdam house market was tight and getting expensive every day.</p><h3 id="dutch-work-culture">Dutch Work Culture</h3><p>Coming from the work culture of the USA, the Dutch work culture was a pleasant shock. Your job is protected by the government, you get a generous pension in case of unemployment, your work-life balance is more than enough to do justice to your career and family, and you are not expected to respond to messages after official work hours. </p><p>If you&apos;re someone who wants to live life and not just &quot;exist&quot;, I strongly recommend the European work culture. However, each person has a different priority, so you can prefer to earn a boatload of money, but still possibly lose your mental health and precious time with your family. </p><h3 id="i-also-offer-a-job-search-and-relocation-consultation">I also offer a Job Search and Relocation Consultation</h3><p>Do you need Advice on making a move and job search in USA or Europe? Are you confused about which country would be suitable for your situation? Do you need help gathering documents for relocation and immigration? Do you want to hear from the person who has moved to USA and Europe and secured multiple job offers in these countries? I am available on TopMate for consultation services. Feel free to book your first free session by visiting <a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">this link</a>.</p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep me writing more articles like this.</p>]]></content:encoded></item><item><title><![CDATA[Say No to AnyView in SwiftUI iOS Applications]]></title><description><![CDATA[Why you should not use AnyView in SwiftUI apps. Downsides and pitfalls of using AnyView in SwiftUI. How to use ViewBuilder in SwiftUI. No AnyView]]></description><link>https://jayeshkawli.com/say-no-to-anyview-in-swiftui-ios-applications/</link><guid isPermaLink="false">648aeb2e79138404fe07bbab</guid><category><![CDATA[Advanced SwiftUI]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[AnyView]]></category><category><![CDATA[Debugging]]></category><category><![CDATA[Optimization]]></category><category><![CDATA[Performance]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Thu, 15 Jun 2023 12:34:09 GMT</pubDate><content:encoded><![CDATA[<p><code>SwiftUI</code> allows developers to use heterogenous view types by making use of <code>AnyView</code> type. <code>AnyView</code> encloses any kind of <code>SwiftUI</code> type into a generic type that can be returned from a function. In this article, we will take a look at,</p><ol><li>Why developers may want to use <code>AnyView</code> type in the application</li><li>What are the downsides of using it</li><li>How can you refactor your app to not use <code>AnyView</code> type</li></ol><h3 id="why-developers-may-want-to-use-anyview-in-the-application">Why developers may want to use AnyView in the application?</h3><p><code>AnyView</code> comes into the picture when you are writing a function that returns heterogenous view types. In that case, you can set your function&apos;s return type as generic <code>some View</code> and cast view to <code>AnyView</code> before returning it. Let&apos;s understand it with an example,</p><pre><code class="language-swift">
struct CarnivoreView: View {
    var body: some View {
        Text(&quot;Carnivore&quot;)
    }
}

struct FishView: View {
    var body: some View {
        Text(&quot;Fish&quot;)
    }
}

struct BirdView: View {
    var body: some View {
        Text(&quot;Bird&quot;)
    }
}

enum Animal {
    case carnivore
    case fish
    case bird
}

.....
...

func topView(for animal: Animal) -&gt; some View {
    switch animal {
    case .carnivore:
        return AnyView(CarnivoreView())
    case .fish:
        return AnyView(FishView())
    case .bird:
        return AnyView(BirdView())
    }
}</code></pre><p>In the above example, the function <code>topView</code> returns the view type depending on the type of animal passed to it. Since it could either be <code>CarnivoreView</code>, <code>FishView</code> or <code>BirdView</code>, we are using <code>some View</code> as a return type and casting them to <code>AnyView</code> before returning it. </p><p>Now that we know why and where <code>AnyView</code> might get used, let&apos;s try to understand some downsides of using it. </p><h3 id="what-are-the-downsides-of-using-it">What are the downsides of using it?</h3><p><code>AnyView</code> is a type-erasing wrapper type. Using <code>AnyView</code> hides the if-else structure of code and the underlying concrete type used for building a view. When the compiler looks at the returned type, all it sees is a <code>AnyView</code> type. This not only makes code hard to read for other developers but can also make it difficult to observe and analyze view structure during debugging. </p><p>For example, when the compiler emits useful compile-time diagnostics, using <code>AnyView</code> makes it hard to understand what&apos;s going on under the hood since all it exposes is an <code>AnyView</code> during the debugging session.</p><p><code>some View</code> = <code>AnyView</code></p><p>Another downside of using <code>AnyView</code> is, it also results in poor app performance since <code>SwiftUI&apos;s</code> view-updating framework cannot really determine which underlying view has changed due to type-erasing <code>AnyView</code> usage. If there is really a need for this use case, please use the generic types to preserve the static type instead of passing <code>AnyView</code> around. </p><h3 id="how-can-you-refactor-your-app-to-avoid-anyview-type">How can you refactor your app to avoid AnyView type?</h3><p>Fortunately, it&apos;s possible to avoid <code>AnyType</code> and also to use different view types using a <code>ViewBuilder</code> construct. <code>ViewBuilder</code> is an annotation that can be applied to view building functions that can return any type of view without casting it to <code>AnyView</code> before returning. The return type is still going to be <code>some View</code>, but it helps preserve underlying type information and the compiler can analyze the internal view structure without losing debugging information.</p><p>To apply <code>ViewBuilder</code> annotation, simply prefix function with <code>@ViewBuilder</code> annotation and remove all the <code>AnyView</code> casts inside <code>topView</code> function.</p><pre><code class="language-swift">
@ViewBuilder func topView(for animal: Animal) -&gt; some View {
    switch animal {
    case .carnivore:
        CarnivoreView()
    case .fish:
        FishView()
    case .bird:
        BirdView()
    }
}

</code></pre><blockquote>When using <code>ViewBuilder</code>, it is also not necessary to specify <code>return</code> keyword for returning the view, so we&apos;ve ommitted it</blockquote><p>Now if you analyze the view structure returned by <code>topView</code>, it will look something like this,</p><pre><code class="language-swift">
some View = _ConditionalContent&lt;
				_ConditionalContent&lt;CarnivoreView, FishView&gt;, 
				BirdView&gt;

</code></pre><p>It allows you to inspect what lies inside the view returned by the function and how the choice of final view is evaluated.</p><h3 id="summary">Summary</h3><p>So that&apos;s all I wanted to talk about why not to use <code>AnyView</code> and how to build views using <code>ViewBuilder</code> in <code>SwiftUI</code> applications. ViewBuilder offers an excellent way to build reusable views, preserve their internal structure while debugging, and also to improve the app performance by helping update only those underlying views whose data might have changed.</p><p>Hopefully, this blog post was helpful for you to understand how to build <code>SwiftUI</code> apps using <code>ViewBuilder</code> and build performant iOS apps. If you have any other questions, comments, or feedback, please feel free to reach out on <a href="https://www.linkedin.com/in/jayeshkawli/?ref=jayeshkawli.com">LinkedIn</a>.</p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep me writing more articles like this.</p><h3 id="consulting-services">Consulting Services</h3><p>I also provide a few consulting services on Topmate.io, and you can reach out to me there too. These services include,</p><ol><li><a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">Let&apos;s Connect</a></li><li><a href="https://topmate.io/jayesh_kawli/62010?ref=jayeshkawli.com">Resume Review</a></li><li><a href="https://topmate.io/jayesh_kawli/62009?ref=jayeshkawli.com">1:1 Mentorship</a></li><li><a href="https://topmate.io/jayesh_kawli/62012?ref=jayeshkawli.com">Interview Preparation &amp; Tips</a></li><li><a href="https://topmate.io/jayesh_kawli/63004?ref=jayeshkawli.com">Conference Speaking</a></li><li><a href="https://topmate.io/jayesh_kawli/215835?ref=jayeshkawli.com">Take-home Exercise Help (iOS)</a></li><li><a href="https://topmate.io/jayesh_kawli/62008?ref=jayeshkawli.com">Career Guidance</a></li><li><a href="https://topmate.io/jayesh_kawli/62011?ref=jayeshkawli.com">Mock Interview</a></li></ol>]]></content:encoded></item><item><title><![CDATA[How to Use Actors on iOS to Achieve Thread Safety]]></title><description><![CDATA[Actors on iOS and Swift. Actors in iOS 13 and Mac 10.15. How to use Actors in Swift to achieve concurrency without data races.]]></description><link>https://jayeshkawli.com/how-to-use-actors-on-ios-to-achieve-thread-safety/</link><guid isPermaLink="false">6488694e79138404fe07b9dc</guid><category><![CDATA[Advanced Swift]]></category><category><![CDATA[Concurrency]]></category><category><![CDATA[Multithreading]]></category><category><![CDATA[Actors]]></category><category><![CDATA[Data Race]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Tue, 13 Jun 2023 14:56:26 GMT</pubDate><media:content url="https://jayeshkawli.com/content/images/2023/06/Screenshot-2023-06-13-at-4.55.50-PM.png" medium="image"/><content:encoded><![CDATA[<img src="https://jayeshkawli.com/content/images/2023/06/Screenshot-2023-06-13-at-4.55.50-PM.png" alt="How to Use Actors on iOS to Achieve Thread Safety"><p>Apple introduced the concept of <a href="https://developer.apple.com/documentation/swift/actor?ref=jayeshkawli.com">Actors</a> in iOS 13.0 and macOS 10.15. Actors allow us to modify the program state in a concurrent environment without data races and the possibility of corrupting data due to simultaneous access. </p><blockquote>Data races happen when two threads access the same data and one of them is a write operation. </blockquote><p>Swift makes it in such a way that there is no way to modify a mutable state simultaneously by two threads at the same time when used with actors. This helps developers write thread-safe code without having to manually control it using locks, atomics, or serial dispatch queues. </p><p>Let&apos;s try to understand multithreading and data race problem with an example, </p><h3 id="data-race-and-threading-issue">Data Race and Threading Issue</h3><pre><code class="language-swift">
class HeartRateCounter {
    var beats = 0

    func incrementBeats() -&gt; Int {
        beats += 1
        return beats
    }
}

</code></pre><p>In the above example, we have a class named <code>HeartRateCounter</code>. It has a single variable named <code>beats</code>. The function <code>incrementBeats</code> increments the value of this variable every time it is called. </p><p>If we&apos;re running it in the single-thread synchronous context, it will work as expected. But if we were to run it in a multi-threaded environment in an async fashion, <code>incrementBeats</code> function will return different values based on the order of execution. </p><pre><code class="language-swift">
let heartRatesCounter = HeartRateCounter()

DispatchQueue.global().async {
    print(&quot;First increment beats \(heartRatesCounter.incrementBeats())&quot;)
}

DispatchQueue.global().async {
    print(&quot;Second increment beats \(heartRatesCounter.incrementBeats())&quot;)
}

</code></pre><p>Prints</p><pre><code>
Second increment beats 2
First increment beats 1

</code></pre><p>If we run it again, it now prints</p><pre><code>
First increment beats 2
Second increment beats 2

</code></pre><p>There is no predictable pattern in order in which print statements will be executed nor the output of return value from <code>incrementBeats</code>calls. </p><p>Actors essentially help us fix this issue by constraining only one access to mutating variables at one time. If two threads try to simultaneously access it while one of them is a write access, one of the threads has to wait before the first operation is completed. </p><h3 id="problem-with-data-races">Problem with Data Races</h3><p>The major issue with data races is not their implication, but the difficulty of reproducing them. It&apos;s easy to artificially create one, but even if you know the data race is happening in the production code, it can be challenging to reproduce it on your local machine. </p><p>So the best defense against them is to prevent them so that you don&apos;t have to deal with the headache of reproducing and fixing them later.</p><h3 id="meet-actors">Meet Actors</h3><p>Swift Actors provide synchronization for shared mutable data by isolating their state from the rest of the program. This results in mutual access to mutable states even in the multi-threaded environment.</p><p>Actors are types in Swift, just like struct, classes, and enums. You can create a new actor using <code>Actor</code> keyword followed by actor&apos;s name. They are reference types and may contain properties, methods, and initializers.</p><blockquote>Actors though, share many similarities with classes, do not support inheritance</blockquote><p>With concepts about Actors clarified, let&apos;s convert the <code>HeartRateCounter</code> class into an <code>Actor</code>. The only thing we change is a keyword from <code>class</code> to <code>actor</code>. Everything else remains the same. </p><pre><code class="language-swift">
actor HeartRateCounter {
    var beats = 0

    func incrementBeats() -&gt; Int {
        beats += 1
        return beats
    }
}


</code></pre><p>Now that <code>Actor</code> is set up, it will restrict multiple threads from calling <code>incrementBeats</code> while another thread is busy executing. We can do it by using <code>async</code> keyword before calling <code>incrementBeats</code>. Now every access to its property and function is an async access. (There are exceptions though - More about that in later part).</p><pre><code class="language-swift">
let heartRatesCounter = HeartRateCounter()

Task {
    print(&quot;First increment beats \(await heartRatesCounter.incrementBeats())&quot;)
    print(&quot;Second increment beats \(await heartRatesCounter.incrementBeats())&quot;)
}

</code></pre><pre><code class="language-swift">
//prints this output every time the program is run

First increment beats 1
Second increment beats 2

</code></pre><p>This makes sure that while the actor is busy evaluating the first expression, the code will suspend and the CPU you&apos;re running on will do other useful work. When the actor becomes free, it will wake up the code and resume the execution again. </p><p>Let&apos;s take another more detailed example here,</p><pre><code class="language-swift">
let heartRatesCounter = HeartRateCounter()

Task.detached {
    print(&quot;First increment beats \(await heartRatesCounter.incrementBeats())&quot;)
}

Task.detached {
    print(&quot;Second increment beats \(await heartRatesCounter.incrementBeats())&quot;)
}

</code></pre><p>In the above example, we&apos;re calling <code>incrementBeats</code> on two detached tasks (two different threads). Although there is no order in which they will be executed, they are guaranteed to have exclusive access to <code>beats</code> variable and will never produce half-mutated value. When run, it can either print</p><pre><code class="language-swift">
First increment beats 1
Second increment beats 2

</code></pre><p>OR</p><pre><code class="language-swift">
Second increment beats 2
First increment beats 1

</code></pre><p>But nothing else. This makes sure one async operation is not interrupted by other thread while read or write operation is in progress and these two pieces of code never run simultaneously and always produce a valid result in the execution order.</p><h3 id="exceptions">Exceptions</h3><p>Using <code>await</code> keyword results in the suspension of the thread and waiting until async operation is finished. However, you may not always need to use the isolated instance and await keyword while accessing data.</p><p>If the actor property is immutable and constant, you can access it without using <code>await</code> keyword. For example, if we delcare a new property named <code>machineName</code> with a constant value, the function that accesses it need not be isolated and have a mutually-exclusive access.</p><pre><code class="language-swift">
actor HeartRateCounter {
    var beats = 0

    let machineName = &quot;US Heart Surgeons Machine&quot;

    func incrementBeats() -&gt; Int {
        beats += 1
        return beats
    }

    func getMachineName() -&gt; String {
        return machineName
    }
}


</code></pre><p>Ideally, when I try to call <code>getMachineName</code>, it should allow me to do so without using <code>await</code> keyword. But unfortunately, in this case, I am getting an error thrown at me.</p><p><code>Expression is &apos;async&apos; but is not marked with &apos;await&apos;</code></p><figure class="kg-card kg-image-card"><img src="https://jayeshkawli.com/content/images/2023/06/Screenshot-2023-06-13-at-4.18.01-PM.png" class="kg-image" alt="How to Use Actors on iOS to Achieve Thread Safety" loading="lazy" width="1802" height="204" srcset="https://jayeshkawli.com/content/images/size/w600/2023/06/Screenshot-2023-06-13-at-4.18.01-PM.png 600w, https://jayeshkawli.com/content/images/size/w1000/2023/06/Screenshot-2023-06-13-at-4.18.01-PM.png 1000w, https://jayeshkawli.com/content/images/size/w1600/2023/06/Screenshot-2023-06-13-at-4.18.01-PM.png 1600w, https://jayeshkawli.com/content/images/2023/06/Screenshot-2023-06-13-at-4.18.01-PM.png 1802w" sizes="(min-width: 720px) 720px"></figure><p>This is because Swift assumes every function and property of the actor needs isolated access. However, this is not the case here. Since <code>machineName</code> is an immutable constant, the function that accesses it can have nonisolated access and can be called without using <code>await</code> keyword. </p><p>We can fix it by prefixing <code>nonisolated</code> keyword to <code>getMachineName</code> function. This explicitly tells the compiler that calling this function in a multithreaded environment will not cause data race.</p><pre><code class="language-swift">
nonisolated func getMachineName() -&gt; String {
    return machineName
}

</code></pre><p>Now with this change in place, let&apos;s run the app and see the output</p><pre><code class="language-swift">
// prints &quot;Machine name is US Heart Surgeons Machine&quot;
print(&quot;Machine name is \(heartRatesCounter.getMachineName())&quot;)

</code></pre><blockquote>Please remember that nonisolated keyword only works on actor functions. If you apply nonisolated to constant declarations, compiler will throw an error since constants are nonisolated by default</blockquote><h3 id="summary">Summary</h3><p>Actors offer a simple and boilerplate-less code to avoid threading issues and data races in a multithreading environment. Earlier, it used to be locks and serial dispatch queues. But it&apos;s often easy to go wrong with them and there is a lot of code involved. With the help of actors and async-await semantics, it becomes easy to detect and avoid data races during development cycle. </p><p>The beauty of this approach is, even if you write a code that might cause data race later, <code>Actor</code> will help you catch that even before you run the app. </p><p>What do you think about these new semantics introduced in Swift? Do you think they offer a better way to avoid data races? Have you tried <code>Actors</code> already? How was your experience with it? I would love to hear your thoughts on it.</p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep me writing more articles like this.</p><h3 id="consulting-services">Consulting Services</h3><p>I also provide a few consulting services on Topmate.io, and you can reach out to me there too. These services include,</p><ol><li><a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">Let&apos;s Connect</a></li><li><a href="https://topmate.io/jayesh_kawli/62010?ref=jayeshkawli.com">Resume Review</a></li><li><a href="https://topmate.io/jayesh_kawli/62009?ref=jayeshkawli.com">1:1 Mentorship</a></li><li><a href="https://topmate.io/jayesh_kawli/62012?ref=jayeshkawli.com">Interview Preparation &amp; Tips</a></li><li><a href="https://topmate.io/jayesh_kawli/63004?ref=jayeshkawli.com">Conference Speaking</a></li><li><a href="https://topmate.io/jayesh_kawli/215835?ref=jayeshkawli.com">Take-home Exercise Help (iOS)</a></li><li><a href="https://topmate.io/jayesh_kawli/62008?ref=jayeshkawli.com">Career Guidance</a></li><li><a href="https://topmate.io/jayesh_kawli/62011?ref=jayeshkawli.com">Mock Interview</a></li></ol>]]></content:encoded></item><item><title><![CDATA[What I Learned from My Recent Experience Creating iOS Tutorial]]></title><description><![CDATA[Learning from recent experience creating tutorials for new iOS developers. Learnings and mistakes from part-time job]]></description><link>https://jayeshkawli.com/what-i-learned-from-my-recent-experience-creating-ios-tutorial/</link><guid isPermaLink="false">6470c1e4da814004d1020e92</guid><category><![CDATA[iOS]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Teaching]]></category><category><![CDATA[Upwork]]></category><category><![CDATA[Swift]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Fri, 26 May 2023 15:06:16 GMT</pubDate><content:encoded><![CDATA[<p>Recently, I was asked to write a tutorial and build an iOS app prototype to teach high-school students how to write iOS apps. The goal was to create a basic project that will include all the basic iOS concepts they&apos;ve learned so far and build the app on these foundations. </p><p>It&apos;s a good feeling that you&apos;re building something that&apos;s helping other folks to learn things. But I underestimated how much work it will involve and how tricky it is. Writing apps is simple. Write the code, run the app, verify the functionality and designs, and make sure everything is in order. </p><p>Writing video tutorials, explaining things and well documenting them is much harder and involves more time and concentration if you want to deliver them professionally. </p><p>Here are some things that I didn&apos;t realize are involved in app tutorials but they were of utmost importance when it comes to the final product delivery.</p><ol><li><strong>Live coding is harder than it sounds</strong></li></ol><p>When I am writing code by myself without anyone watching, I am much more at ease. But if someone is looking at the screen while I am writing or when I am recording the videos and talking and writing code simultaneously, I need to be more conscious of my actions, the things I do, and the surrounding distractions. If something went wrong, I needed to cut off and start all over again. So perfection was the key and failure was not an option. </p><p><strong>2. Creating transcription is hard</strong></p><p>At the end of the tutorial, I was also asked to provide a transcript of the audio. I thought I can easily do that by listening to myself and simultaneously typing what I was hearing in the headphone. But turned out, I was talking faster than I could write and had to pause every 5 seconds. This is fine for short videos, but the final video was 3 hours long and this definitely wasn&apos;t feasible.</p><p>Fortunately, there are many online AI tools that extract transcripts from audio files. You need to use them with caution. First, the free plans are limited and beyond that, you need to pay per minute - Which in my case was going to be too expensive for 180 minutes. </p><p>Second, these tools aren&apos;t perfect and may produce a slightly different transcript. So you still need to check it all out before publishing the transcript</p><p><strong>3. Coming up with an idea for an app</strong></p><p>When I am building a random app of my own, I don&apos;t care about its viability, clarity, or its adoption by real-world users. However, when you&apos;re building something to teach, you need to convince learners about its usage, real-world benefit it offers, and ease of understanding as to why this app would be accepted by users. </p><p>For example, if I had built a prototype app of say, a toast sandwich, that caters to a niche population, not a lot of people may relate to it. But if I tackle a real-world problem such as a grocery items list, movie rating app, or trash finder, folks can relate to it and will use it as an inspiration to build other practical apps helping people around them.</p><p><strong>4. Building features vs. Keeping it simple</strong></p><p>It is very easy to fall into the trap that you&apos;re cramming every API and every concept there is to help students learn faster. But that won&apos;t do good. You need to keep things simple as they are still learning the foundations of iOS development. </p><p>But if you keep it too simple, they won&apos;t learn anything either. So how do you strike that balance? In my case, I reached out to the instructor and went through their goals and learning objectives. Made a list of things they were taught and I incorporated them. On some occasions, I went slightly beyond what was taught in the course to give them a holistic understanding but complemented well with what they already knew. </p><p><strong>5. Beware of online file transfer tools</strong></p><p>Not all tools are created equally. When I was finished recording, I had around 35 video files. I thought managing them individually is a tedious task, so I combined them all using QuickTime and sent them over to the instructor in one giant file.</p><p>When they downloaded this huge file on their end, they reported that the audio and video were out of sync. I figured something might have gone wrong during the uploading and processing of this huge file. </p><p>I had to go back and cut this file into smaller chunks before sending it again. This approach worked and the files were received without any corruption.</p><p><strong>6. Divide the video into logical segment</strong></p><p>Giving a single video file or random chunks without logical segments can easily overwhelm the person who has no context on the topic. In my case, although the video was divided into several segments, it wasn&apos;t divided logically. For example, when you build a house, you build a foundation, then walls, then roof, then install heating and water connections, etc. In my case, the division didn&apos;t really make sense.</p><p>I had to go back and watch the full video, split it into logical segments, and provide timestamps for each of these segments. For example, creating a new project, Xcode UI overview, adding a new file, building a screen, adding navigation, and so on. It made learning so much smooth and if students wanted to skip any of the sections for later, they could easily do that with the help of provided timestamps.</p><h3 id="summary">Summary</h3><p>In summary, I would say teaching is hard. Just because you know something in detail or you&apos;ve been working in the field for a while doesn&apos;t translate into your ability to convey that knowledge in a clear and concise manner. </p><p>However, after going through this experience, I have now realized my (many) mistakes, and received nice feedback at the end with this foundation, I am prepared to create many such videos to make iOS Development Learning easier and accessible to students all around the world. </p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep me writing more articles like this.</p>]]></content:encoded></item><item><title><![CDATA[Why I Shifted from Ghost to Self-hosting for My Blog?]]></title><description><![CDATA[Why I moved away from Ghost hosting. How to migrate a blog from Ghost to DigitalOcean hosting. Ghost blogging tips. Save money on Ghost blog]]></description><link>https://jayeshkawli.com/why-i-shifted-from-ghost-to-self-hosting-for-my-blog/</link><guid isPermaLink="false">647087c2da814004d1020e08</guid><category><![CDATA[Ghost]]></category><category><![CDATA[Blog]]></category><category><![CDATA[Migration]]></category><category><![CDATA[Transition]]></category><category><![CDATA[cost]]></category><category><![CDATA[digitalOcean]]></category><category><![CDATA[digital]]></category><category><![CDATA[ocean]]></category><category><![CDATA[blogging]]></category><category><![CDATA[Hosting Provider]]></category><category><![CDATA[hosting]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Fri, 26 May 2023 10:43:30 GMT</pubDate><content:encoded><![CDATA[<p>Recently, I made a move to self-hosting on DigitalOcean away from hosting my blog on Ghost&apos;s servers. The ride wasn&apos;t exactly smooth, but all the support from Ghost and DigitalOcean customer service members still made the transition possible. I have been a Ghost customer for the past 7 years and have been thinking about this transition for a while.</p><p>The first thing was definitely the cost. A couple of years ago, Ghost automatically upgraded my plan to a more expensive tier when the traffic grew. The shady thing is, they do it for the rest of the year and charge you upfront even if traffic goes down in subsequent months. For example, if my site traffic goes above 10k visitors on any given day in a month, Ghost will auto-upgrade my account from say $10 to $30 tier and pro-rated charge me a bill of $30 per month for the rest of the year. If my traffic drops to say 1k users per month for the rest of the year, I still have to foot the bill for 10k / month of traffic. This definitely wasn&apos;t financially sustainable.</p><p>Second, I didn&apos;t really see any benefit in hosting all the content on the Ghost server. Of course, they provide higher bandwidth, server maintenance, and built-in services, but there was nothing I couldn&apos;t do on my own server with additional efforts. Kudos to Ghost for making it open-source and providing us with an excellent documentation and customer support team. So in one way, they were asking tech-savvy users to migrate away from Ghost hosting - Possibly to reduce their own cost as their user base is growing.</p><p>Third, I never set up my own server. I heard people boasting about how they pay $5 / month on DigitalOcean to run their own website. I was excited about that. I have mainly been a front-end developer and always shrugged away from back-end development. This project was my one chance to get my hands dirty setting up a domain and a self-hosted blog. It was a struggle, but after a lot of time, some help, and self-help, I was able to transfer everything to DigitalOcean. (Not to mention $100 in credit for the first 2 months)</p><h3 id="do-you-need-help-transfer-your-ghost-blog-to-a-self-hosted-space-on-digitalocean">Do You Need Help Transfer Your Ghost Blog to a Self-hosted Space on DigitalOcean?</h3><p>Although I struggled during this transition, you don&apos;t have to do the same. If you also want to migrate your Ghost blog from Ghost Hosting to the DigitalOcean ecosystem, feel free to message me. I have notes of my own and I learned from many mistakes. I will be more than happy to help you out with my expertise and experience for the transition.</p><p>If done correctly, including a few mistakes, the transition will take less than a day to complete. So your new site will be ready to run the same day.</p><h3 id="summary">Summary</h3><p>Cost is one of the constraints in today&apos;s economy and unfortunately, it has also affected me. If I were making any money from my blog to break even, I would&apos;ve stayed with Ghost. But given that there are no returns, I made a transition to a cheaper alternative at the cost of service quality. (Website now loads slower than it was on Ghost). </p><p>Right now, it&apos;s a wise decision. Who knows, things might change, there may be more traffic and I need to scale up. In that case, I may have to move back to the Ghost server. But for now, I am happy. </p><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep me writing more articles like this.</p>]]></content:encoded></item><item><title><![CDATA[iOS - Convert Array to String and String to Array in Swift]]></title><description><![CDATA[How to convert array to string in Swift. Convert string to an array in iOS and Swift. Conversion code iOS app. joined and components separatedBy]]></description><link>https://jayeshkawli.com/ios-convert-array-to-string-and-string-to-array-in-swift/</link><guid isPermaLink="false">646b872ce0ec034518585878</guid><category><![CDATA[iOS]]></category><category><![CDATA[Advanced Swift]]></category><category><![CDATA[Swift]]></category><category><![CDATA[Array]]></category><category><![CDATA[String]]></category><category><![CDATA[conversion]]></category><category><![CDATA[New APIs]]></category><category><![CDATA[iOS API]]></category><dc:creator><![CDATA[Jayesh Kawli]]></dc:creator><pubDate>Mon, 22 May 2023 16:50:56 GMT</pubDate><media:content url="https://jayeshkawli.com/content/images/2023/05/Screenshot-2023-05-22-at-6.50.40-PM.png" medium="image"/><content:encoded><![CDATA[<img src="https://jayeshkawli.com/content/images/2023/05/Screenshot-2023-05-22-at-6.50.40-PM.png" alt="iOS - Convert Array to String and String to Array in Swift"><p>Some of the most frequent operations in iOS involve converting an array to a string and string back to an array. This happens when the user has an array of objects but needs to convert them into the user-facing message, or a long string separated by a fixed delimiter needs to be converted into array-based tokens. </p><p>Despite this frequent usage, iOS doesn&apos;t have easily memorable names for these APIs. In today&apos;s blog post, I am going to talk about standard iOS APIs to convert an array to a string and then string back to the array.</p><ol><li><strong>Converting Array to String in iOS using Swift</strong></li></ol><p>To convert a Swift string array to a single String, we are going to use <code>joined</code> method on Swift arrays. You must use the array of strings to use this method.</p><p>This method takes optional <code>separator</code> parameter. If no <code>separator</code> parameter is specified, all the array elements are joined without any space between them.</p><p>For example,</p><pre><code class="language-swift">
let names: [String] = [&quot;Bob&quot;, &quot;Tom&quot;, &quot;Steve&quot;]

let joinedNamesWithSeparator = names.joined(separator: &quot;, &quot;)
print(joinedNamesWithSeparator) // prints Bob, Tom, Steve

let joinedNamesWithoutSeparator = names.joined()
print(joinedNamesWithoutSeparator) // prints BobTomSteve</code></pre><p>Since this API only works with string arrays as specified in the documentation below, </p><pre><code class="language-swift">
// Taken from official Apple documentation

extension Array where Element == String {
    public func joined(separator: String = &quot;&quot;) -&gt; String
}</code></pre><p>you might wonder how to convert an array of custom objects to a string. You can easily do it by mapping an array of custom objects to an array of strings and then calling this API to get a single string back.</p><p>Let&apos;s understand it with an example below,</p><pre><code class="language-swift">
let people: [Person] = [Person(firstName: &quot;Top&quot;, lastName: &quot;Holland&quot;), Person(firstName: &quot;Doctor&quot;, lastName: &quot;Strange&quot;), Person(firstName: &quot;Tony&quot;, lastName: &quot;Stark&quot;)]

let joinedNamesWithSeparator = people.map { $0.firstName }.joined(separator: &quot;, &quot;)

// prints Top, Doctor, Tony
print(joinedNamesWithSeparator)

let joinedNamesWithoutSeparator = people.map { $0.firstName }.joined()

// prints TopDoctorTony
print(joinedNamesWithoutSeparator)</code></pre><p>And that&apos;s it. Now you can convert either an array of strings or an array of custom objects to a string. </p><p><strong>2. &#xA0;Converting String to Array in iOS using Swift</strong></p><p>In this next part, we are going to see how to convert a string into an array of strings. To convert single strings into an array of strings, you need to specify the delimiter which will be used to divide a single string into multiple tokens in the form of an array.</p><p>For example, consider this - </p><pre><code class="language-swift">
let namesCollection = &quot;Top, Doctor, Tony&quot;

let namesArray = namesCollection.components(separatedBy: &quot;,&quot;)

// Prints [&quot;Top&quot;, &quot; Doctor&quot;, &quot; Tony&quot;]
print(namesArray)</code></pre><p>We had a single string <code>&quot;let namesCollection = &quot;Top, Doctor, Tony&quot;</code> where each substring was separated by a character <code>,</code>. We used <code>components(separatedBy</code> API to tokenize this string into an array separated by <code>,</code> so that each substring separated by <code>,</code> became an individual element in the array.</p><p>If the separator character does not exist in the string, this API returns an array with a single element which is, the original string back.</p><pre><code class="language-swift">
let namesCollection = &quot;Top, Doctor, Tony&quot;

let namesArray = namesCollection.components(separatedBy: &quot;:&quot;)

// prints [&quot;Top, Doctor, Tony&quot;]
print(namesArray)</code></pre><h3 id="summary">Summary</h3><p>And that&apos;s all folks. Next time when you run into converting an array to a string or a string to an array conversion, you can use these APIs as a handy tool. To summarize, here&apos;s how you can use them.</p><pre><code class="language-swift">
let names: [String] = [&quot;Bob&quot;, &quot;Tom&quot;, &quot;Steve&quot;]

let joinedNamesWithSeparator = names.joined(separator: &quot;, &quot;)

// prints Bob, Tom, Steve
print(joinedNamesWithSeparator)

let namesCollection = &quot;Top, Doctor, Tony&quot;

let namesArray = namesCollection.components(separatedBy: &quot;, &quot;)

// prints [&quot;Top&quot;, &quot;Doctor&quot;, &quot;Tony&quot;]
print(namesArray)

</code></pre><h3 id="support-and-feedback">Support and Feedback</h3><p>If you have any comments or questions, please feel free to reach out to me on <a href="https://www.linkedin.com/in/jayesh-kawli-8aa22633/?ref=jayeshkawli.com">LinkedIn</a>.</p><p>If you like my blog content and wish to keep me going, please consider donating on <a href="https://www.buymeacoffee.com/jkawliA?ref=jayeshkawli.com">Buy Me a Coffee</a> or <a href="https://www.patreon.com/jayeshkawli?ref=jayeshkawli.com">Patreon</a>. Help, in any form or amount, is highly appreciated and it&apos;s a big motivation to keep me writing more articles like this.</p><h3 id="consulting-services">Consulting Services</h3><p>I also provide a few consulting services on Topmate.io, and you can reach out to me there too. These services include,</p><ol><li><a href="https://topmate.io/jayesh_kawli/62007?ref=jayeshkawli.com">Let&apos;s Connect</a></li><li><a href="https://topmate.io/jayesh_kawli/62010?ref=jayeshkawli.com">Resume Review</a></li><li><a href="https://topmate.io/jayesh_kawli/62009?ref=jayeshkawli.com">1:1 Mentorship</a></li><li><a href="https://topmate.io/jayesh_kawli/62012?ref=jayeshkawli.com">Interview Preparation &amp; Tips</a></li><li><a href="https://topmate.io/jayesh_kawli/63004?ref=jayeshkawli.com">Conference Speaking</a></li><li><a href="https://topmate.io/jayesh_kawli/215835?ref=jayeshkawli.com">Take-home Exercise Help (iOS)</a></li><li><a href="https://topmate.io/jayesh_kawli/62008?ref=jayeshkawli.com">Career Guidance</a></li><li><a href="https://topmate.io/jayesh_kawli/62011?ref=jayeshkawli.com">Mock Interview</a></li></ol>]]></content:encoded></item></channel></rss>