<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/assets/feed.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="https://delightfulapps.co/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Delightful Apps</title>
    <link>https://delightfulapps.co/</link>
    <atom:link href="https://delightfulapps.co/rss.xml" rel="self" type="application/rss+xml" />
    <description>Writing from Delightful Apps.</description>
    <language>en</language>
    <item>
      <title>Mobile Builds release 2026.6</title>
      <link>https://delightfulapps.co/blog/mobile-builds-2026-6/</link>
      <description>&lt;p&gt;The next update for &lt;a href=&quot;https://delightfulapps.co/products/mobilebuilds&quot;&gt;Mobile Builds&lt;/a&gt; is now out and available on the &lt;a href=&quot;https://apps.apple.com/us/app/mobile-builds/id6768527314&quot;&gt;App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This build makes it super easy for everyone to see what&#39;s going on with their Xcode Cloud builds and workflows. It&#39;s super fun to have worked on this build.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;New features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Siri&lt;/li&gt;
&lt;li&gt;Shortcuts&lt;/li&gt;
&lt;li&gt;Spotlight search&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Mon, 17 Aug 2026 24:00:00 GMT</pubDate>
      <dc:creator>Delightful Apps</dc:creator>
      <guid>https://delightfulapps.co/blog/mobile-builds-2026-6/</guid>
    </item>
    <item>
      <title>Indie Dev Journal - week ending August 16th 2026</title>
      <link>https://delightfulapps.co/blog/indie-dev-journal/</link>
      <description>&lt;p&gt;So I&#39;m trying something new today. My focus for the past couple of weeks has been on indie development and getting some of my own apps updated ahead of the September OS release from Apple. So let&#39;s have some fun, break down what&#39;s going on and learn from each other.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://delightfulapps.co/products/previewsmith&quot;&gt;PreviewSmith&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://previewsmith.app&quot;&gt;https://previewsmith.app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&#39;s release time. The app is now live on the app store after a lot of work getting the interactions just right.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Live on the &lt;a href=&quot;https://apps.apple.com/us/app/previewsmith/id6779501249&quot;&gt;App Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fixed up interactions between the app and the helper.&lt;/li&gt;
&lt;li&gt;Made some tweeks to the UI.&lt;/li&gt;
&lt;li&gt;AppStore copy on the release view.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;https://delightfulapps.co/products/mobilebuilds&quot;&gt;Mobile Builds&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://mobilebuilds.app&quot;&gt;https://mobilebuilds.app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Everything is gearing up to a new release in the next couple of days. I&#39;ve been busy at work to get AppIntents (siri and shortcuts) going ahead of September. These are looking amazing and some great ways of interacting with the data.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://delightfulapps.co/products/ridejournal&quot;&gt;Ride Journal&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ridejournal.app&quot;&gt;https://ridejournal.app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lots of fixes in the works here and new stuff is on the way. Once Mobile Builds is out, I&#39;ll be working on Ride Journal to get these features out.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Climb detection on rides.&lt;/li&gt;
&lt;li&gt;Radar details.&lt;/li&gt;
&lt;li&gt;Bug fixes and improvements to Apple Health integrations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Contract Work&lt;/h2&gt;
&lt;p&gt;Contract work has been quiet for a few weeks which means the focus is on indie apps. If you want to have a chat or know about anyone who needs an amazing iOS / macOS / visionOS / watchOS developer to build an app or be part of a team then get in touch.&lt;/p&gt;
&lt;h2&gt;Blog posts and writings&lt;/h2&gt;
&lt;p&gt;Done a bit of blogging recently over on my personal site about what I&#39;m learning. It&#39;s a lot of fun.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cuteios.dev/2026/08/12/call-me-any-time/&quot;&gt;Have the SEO call you&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cuteios.dev/2026/08/02/fear-of-seo/&quot;&gt;Don&#39;t fear the SEO&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sun, 16 Aug 2026 07:00:00 GMT</pubDate>
      <dc:creator>Delightful Apps</dc:creator>
      <guid>https://delightfulapps.co/blog/indie-dev-journal/</guid>
    </item>
    <item>
      <title>Authenticating with a web service</title>
      <link>https://delightfulapps.co/blog/authenticating-with-a-web-service/</link>
      <description>&lt;p&gt;Knowing who you are is a difficult thing at the best of times. Allowing an app to authenticate against a web service is likewise as difficult due to the level of trust that needs to be created between you and the system handling the authentication. Thankfully Apple has a solution to the authentication problem known as &lt;a href=&quot;https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession&quot;&gt;ASWebAuthenticationSession&lt;/a&gt; that exists as part of the &lt;a href=&quot;https://developer.apple.com/documentation/authenticationservices&quot;&gt;AuthenticationServices&lt;/a&gt; framework.&lt;/p&gt;
&lt;p&gt;So what makes authentication a difficult? Can&#39;t we just send username and password in the clear? The answer to that is simple. No, we can&#39;t. We need to make sure that who we are authenticating against is who we expect. We need to make sure that we create the trust between our app and the web service. Let&#39;s look at how this is done.&lt;/p&gt;
&lt;p&gt;The first component we want is &lt;code&gt;ASWebAuthenticationSession&lt;/code&gt;, though in order to make use of that we need to build a few things first. By doing this, we are able to expose parts of AppKit or UIKit to SwiftUI so that &lt;code&gt;ASWebAuthenticationSession&lt;/code&gt; does it thing. So what does it require? It requires a &lt;code&gt;ASPresentationAnchor&lt;/code&gt;. This typically is a window being either an &lt;code&gt;NSWindow&lt;/code&gt; or an &lt;code&gt;UIWindow&lt;/code&gt;. In the land of SwiftUI, this is done through an environment variable. Let&#39;s build this out.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct WindowKey: EnvironmentKey {
    static let defaultValue: NSWindow? = nil
}

extension EnvironmentValues {
    var window: NSWindow? {
        get {
            self[WindowKey.self]
        }
        set {
            self[WindowKey.self] = newValue
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So what exactly does that do? It provides our SwiftUI views with the knowledge of the window and we&#39;ll use that later on. We&#39;ll move on and start building out the app parts. First up is the view that will allow us to present everything. As you guessed, it&#39;s a button that we tap.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct AuthView: View {
  @Environment(&#92;.window) var window: NSWindow?
  let store: AuthStore

  var body: some View {
    VStack(alignment: .center, spacing: 16.0) {
      Button(action: {
        self.store.auth()
      }) {
        Text(&amp;quot;Login with GitHub&amp;quot;)
          .font(.headline)
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a few bits we need to fill out here, the first of which is our &lt;code&gt;AuthStore&lt;/code&gt;. Lets look at this and how it&#39;s pieced together.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;final class AuthStore {
  private var helper: AuthHelper?
  private var authCancellable: AnyCancellable? {
    willSet {
      authCancellable?.cancel()
    }
  }

  init(window: NSWindow?) {
    helper = AuthHelper(window: window)
  }

  func auth() {
    authCancellable = helper?.authenticate()
      .sink(receiveCompletion: { completion in

      }, receiveValue: { apiKey in
        if AuthKeychainHelper.tokenExists {
          try? AuthKeychainHelper.update(token: apiKey)
        } else {
          try? AuthKeychainHelper.store(token: apiKey)
        }
      })
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This involves one piece that we wont touch on being the Keychain helper. Suffice to say, it&#39;s for storing the resulting api token. The piece of interest though is the &lt;code&gt;AuthHelper&lt;/code&gt; which handles our interaction with &lt;code&gt;ASWebAuthenticationSession&lt;/code&gt;. The full implementation will be placed at the end, but for now we&#39;ll break apart the interesting pieces.&lt;/p&gt;
&lt;p&gt;To begin, we have the type that conforms to &lt;code&gt;ASWebAuthenticationPresentationContextProviding&lt;/code&gt; and tells us how the web auth window should be presented. When dealing with &lt;code&gt;ASWebAuthenticationSession&lt;/code&gt; we want to be providing it with an instance of this type.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class AuthServiceContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding {
  let window: NSWindow

  init(window: NSWindow) {
    self.window = window
  }

  func presentationAnchor(for session: ASWebAuthenticationSession) -&amp;gt; ASPresentationAnchor {
    return window
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second part is our call to &lt;code&gt;ASWebAuthenticationSession&lt;/code&gt;. We are wrapping this in a Future so that it&#39;s exposed to SwiftUI using Combine.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;  func authenticate() -&amp;gt; AnyPublisher&amp;lt;String, Errors&amp;gt; {
    let scopes = [
      &amp;quot;user&amp;quot;,
      &amp;quot;public_repo&amp;quot;,
      &amp;quot;repo&amp;quot;,
      &amp;quot;repo:status&amp;quot;,
      &amp;quot;read:org&amp;quot;
    ]

    var urlComponents = URLComponents(string: &amp;quot;&amp;quot;)
    urlComponents?.queryItems = [
      URLQueryItem(name: &amp;quot;client_id&amp;quot;, value: &amp;quot;abc123&amp;quot;),
      URLQueryItem(name: &amp;quot;scope&amp;quot;, value: scopes.joined(separator: &amp;quot; &amp;quot;))
    ]

    guard let url = urlComponents?.url else {
      return Fail(error: Errors.invalidURL).eraseToAnyPublisher()
    }

    return Future&amp;lt;String, Errors&amp;gt; { promise in
      let session = ASWebAuthenticationSession(url: url, callbackURLScheme: &amp;quot;appscheme&amp;quot;) { (url, error) in
        if let error = error as? ASWebAuthenticationSessionError {
          switch error.code {
          case .canceledLogin:
            promise(.failure(.userCancelled))
          default:
            promise(.failure(.unknown))
          }
        }

        if let url = url, let oauthToken = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.filter({ $0.name == &amp;quot;code&amp;quot; }).first?.value {
          promise(.success(oauthToken))
        } else {
          promise(.failure(.noToken))
        }
      }

      session.presentationContextProvider = self.contextProvider
      session.start()
    }
    .flatMap(fetchToken(with:))
    .receive(on: RunLoop.main)
    .eraseToAnyPublisher()
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we have asked &lt;code&gt;ASWebAuthenticationSession&lt;/code&gt; to authenticate against the given URL (in my projects case, GitHub). The second interesting part is what we do with the token we get back. Unfortunately what GitHub returns us isn&#39;t the final api token we use for requests. We need to make another call to get that. This happens in the &lt;code&gt;fetchToken(with:)&lt;/code&gt; function. Let&#39;s unpack that now.&lt;/p&gt;
&lt;p&gt;There&#39;s a curiosity here in the code. What exactly is the &lt;code&gt;callbackURLScheme&lt;/code&gt;? This is how the service knows to call back into your app once you have been authenticated. You&#39;ll need to set this up in your info.plist. The scheme you define there is the one you provide here.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;  private func fetchToken(with token: String) -&amp;gt; AnyPublisher&amp;lt;String, Errors&amp;gt; {
    var components = URLComponents(string: &amp;quot;&amp;quot;)
    let queryItems: [URLQueryItem] = [
      URLQueryItem(name: &amp;quot;client_id&amp;quot;, value: &amp;quot;123abc&amp;quot;),
      URLQueryItem(name: &amp;quot;client_secret&amp;quot;, value: &amp;quot;abc123&amp;quot;),
      URLQueryItem(name: &amp;quot;code&amp;quot;, value: token)
    ]

    components?.queryItems = queryItems

    guard let url = components?.url else {
      return Fail(error: .invalidURL).eraseToAnyPublisher()
    }

    var request = URLRequest(url: url)
    request.addValue(&amp;quot;application/json&amp;quot;, forHTTPHeaderField: &amp;quot;Accept&amp;quot;)
    request.httpMethod = &amp;quot;POST&amp;quot;

    return URLSession.shared
      .dataTaskPublisher(for: request)
      .map { $0.data }
      .decode(type: Response.self, decoder: decoder)
      .mapError { error in
        if let error = error as? Errors {
          return error
        }

        return Errors.jsonDecoding
      }
      .map { $0.accessToken }
      .eraseToAnyPublisher()
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result of this is our api token after having been authenticated with the web service.&lt;/p&gt;
&lt;p&gt;As mentioned, the entire implementation of the auth helper is included.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class AuthHelper {
  struct Response: Decodable {
    let accessToken: String
    let scope: String
    let tokenType: String

    enum CodingKeys: String, CodingKey {
      case accessToken = &amp;quot;access_token&amp;quot;
      case scope
      case tokenType = &amp;quot;token_type&amp;quot;
    }
  }

  enum Errors: Error {
    case invalidURL
    case userCancelled
    case noToken
    case unknown
    case jsonDecoding
  }

  private var contextProvider: AuthServiceContextProvider
  private var decoder: JSONDecoder

  init?(window: NSWindow?, environment: AppEnvironment) {
    guard let window = window else { return nil }

    contextProvider = AuthServiceContextProvider(
      window: window
    )
    self.decoder = JSONDecoder()
  }

  func authenticate() -&amp;gt; AnyPublisher&amp;lt;String, Errors&amp;gt; {
    let scopes = [
      &amp;quot;user&amp;quot;,
      &amp;quot;public_repo&amp;quot;,
      &amp;quot;repo&amp;quot;,
      &amp;quot;repo:status&amp;quot;,
      &amp;quot;read:org&amp;quot;
    ]

    var urlComponents = URLComponents(string: &amp;quot;&amp;quot;)
    urlComponents?.queryItems = [
      URLQueryItem(name: &amp;quot;client_id&amp;quot;, value: &amp;quot;&amp;quot;),
      URLQueryItem(name: &amp;quot;scope&amp;quot;, value: scopes.joined(separator: &amp;quot; &amp;quot;))
    ]

    guard let url = urlComponents?.url else {
      return Fail(error: Errors.invalidURL).eraseToAnyPublisher()
    }

    return Future&amp;lt;String, Errors&amp;gt; { promise in
      let session = ASWebAuthenticationSession(url: url, callbackURLScheme: &amp;quot;&amp;quot;) { (url, error) in
        if let error = error as? ASWebAuthenticationSessionError {
          switch error.code {
          case .canceledLogin:
            promise(.failure(.userCancelled))
          default:
            promise(.failure(.unknown))
          }
        }

        if let url = url, let oauthToken = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.filter({ $0.name == &amp;quot;code&amp;quot; }).first?.value {
          promise(.success(oauthToken))
        } else {
          promise(.failure(.noToken))
        }
      }

      session.presentationContextProvider = self.contextProvider
      session.start()
    }
    .flatMap(fetchToken(with:))
    .receive(on: RunLoop.main)
    .eraseToAnyPublisher()
  }

  private func fetchToken(with token: String) -&amp;gt; AnyPublisher&amp;lt;String, Errors&amp;gt; {
    var components = URLComponents(string: &amp;quot;&amp;quot;)
    let queryItems: [URLQueryItem] = [
      URLQueryItem(name: &amp;quot;client_id&amp;quot;, value: &amp;quot;&amp;quot;),
      URLQueryItem(name: &amp;quot;client_secret&amp;quot;, value: &amp;quot;&amp;quot;),
      URLQueryItem(name: &amp;quot;code&amp;quot;, value: token)
    ]

    components?.queryItems = queryItems

    guard let url = components?.url else {
      return Fail(error: .invalidURL).eraseToAnyPublisher()
    }

    var request = URLRequest(url: url)
    request.addValue(&amp;quot;application/json&amp;quot;, forHTTPHeaderField: &amp;quot;Accept&amp;quot;)
    request.httpMethod = &amp;quot;POST&amp;quot;

    return URLSession.shared
      .dataTaskPublisher(for: request)
      .map { $0.data }
      .decode(type: Response.self, decoder: decoder)
      .mapError { error in
        if let error = error as? Errors {
          return error
        }

        return Errors.jsonDecoding
      }
      .map { $0.accessToken }
      .eraseToAnyPublisher()
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I hope this has been an insightful article. One that allows you to build some amazing products. If you are after help in such amazing applications, please do get in touch using the links below.&lt;/p&gt;
</description>
      <pubDate>Mon, 09 Mar 2020 07:00:00 GMT</pubDate>
      <dc:creator>Delightful Apps</dc:creator>
      <guid>https://delightfulapps.co/blog/authenticating-with-a-web-service/</guid>
    </item>
    <item>
      <title>Transitions in SwiftUI</title>
      <link>https://delightfulapps.co/blog/transitions-in-swiftui/</link>
      <description>&lt;p&gt;What makes an app delightful? Everyone has a different opinion but when you perform an interaction, the transition needs to be smooth. It has to respond quickly. It must make use of the behaviours that are part of the system. When Apple introduced iOS 13 in 2019, they introduced a change to the way that modals get presented. Gone are the days of a modal covering the entirity of the screen, now is the time of a sheet. A sheet is slightly different in that it is presented almost to the top of the screen. This is a good change and one that allows for the user to understand a part of the app is presented over another and is meant to be a short lived interaction which can be dismissed at will to get back to the main content of the app.&lt;/p&gt;
&lt;p&gt;So what if this isn&#39;t the behaviour we want? What if, we want to change the users context from A to B without the ability to dismiss B to get back to A? Well, that&#39;s a bit more complex. We need to get a little bit fancy. So, sit back, relax with a drink, place on your favourite record and lets explore what&#39;s involved in creating this interaction.&lt;/p&gt;
&lt;p&gt;To understand the structure of the views, we&#39;ll take a look at the hierarchy of the views involed. In this article, we&#39;ll be using SwiftUI. First up, let&#39;s look at the canvas we&#39;ll be displaying things on.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;ZStack(alignment: .center) {
  Text(&amp;quot;Hello world&amp;quot;)
}
.edgesIgnoringSafeArea(.all)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This places a view on the screen that is extended to the absolute edges of the screen. It ignores the safe area insets. But this is just a single view that&#39;s representing our main app context. The A state. Now lets add in our B state. The modal that covers all of A.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;ZStack(alignment: .center) {
  Text(&amp;quot;Hello world&amp;quot;)

  ZStack(alignment: .center) {
    Text(&amp;quot;You&#39;re awesome&amp;quot;)
  }
  .frame(maxWidth: .infinity, maxHeight: .infinity)
  .background(Color.blue)
  .edgesIgnoringSafeArea(.all)
}
.edgesIgnoringSafeArea(.all)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we have the second ZStack covering all of the first. But there&#39;s no interactivity between the two. They are simply layered on top of each other. In order to have the B state displayed as a result of an interaction, we must introduce some state. This is done by using the @State property wrapper around a boolean. This will give us our show / hide behaviour of the B state. It looks like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
  @State var isPresented: Bool = false
  var body: some View {
    ZStack {
      Button(action: {
        self.isPresented.toggle()
      }) {
        Text(&amp;quot;Hello, World!&amp;quot;)
      }

      if isPresented {
        ZStack(alignment: .center) {
          Button(action: {
            self.isPresented.toggle()
          }) {
            Text(&amp;quot;Dismiss modal!&amp;quot;)
              .foregroundColor(.black)
          }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.blue)
        .edgesIgnoringSafeArea(.all)
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us our show / hide of the view but it doesn&#39;t include any delight. There&#39;s no animation. There&#39;s no transition. It just appears, then it disappears. The tap of the screen, the instantaneous change. Nothing smooth about it. We are provided with some handy view modifiers which give us control over these behaviours. These modifiers are &lt;code&gt;.animation&lt;/code&gt; and &lt;code&gt;.transition&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&#39;s explore these modifiers a bit. First, &lt;code&gt;.animation&lt;/code&gt;. What this provides us with is the timing curve over which a view is changed. &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/animation&quot;&gt;Apple&#39;s documentation&lt;/a&gt; provides us with a list of the properties and functions that can be applied. There&#39;s also a well written tutorial that steps through the behaviour &lt;a href=&quot;https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions&quot;&gt;from apple&lt;/a&gt;. There&#39;s also a great article from &lt;a href=&quot;https://www.hackingwithswift.com/books/ios-swiftui/customizing-animations-in-swiftui&quot;&gt;Paul Hudson&lt;/a&gt; on hacking with swift about it as well.&lt;/p&gt;
&lt;p&gt;So what does that give us? It lets us say at what speed we would like the view to be animated in and out. If we want to get fancy, we can even specify a spring animation very similar to what is available in UIKit.&lt;/p&gt;
&lt;p&gt;Adding &lt;code&gt;.animation&lt;/code&gt; to our view is only part of the solution. We also need to say how we want the transition to happen. This modifier takes an &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/anytransition&quot;&gt;AnyTransition&lt;/a&gt; type that specifies the style of transition. There are a lot of predefined options here. These even include the ability to combine transitions and have a different transition for insertion and removal. The transition we are after is move and we want it to happen from the bottom edge. This is &lt;code&gt;.move(edge: .bottom)&lt;/code&gt;. Placing all of these together we get a view like the following.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
  @State var isPresented: Bool = false
  var body: some View {
    ZStack {
      Button(action: {
        withAnimation {
          self.isPresented.toggle()
        }
      }) {
        Text(&amp;quot;Hello, World!&amp;quot;)
      }

      if isPresented {
        ZStack(alignment: .center) {
          Button(action: {
            withAnimation {
              self.isPresented.toggle()
            }
          }) {
            Text(&amp;quot;Dismiss modal!&amp;quot;)
              .foregroundColor(.black)
          }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.blue)
        .edgesIgnoringSafeArea(.all)
        .transition(.move(edge: .bottom))
        .animation(.easeInOut)
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://delightfulapps.co/assets/images/modal_transition.gif&quot; alt=&quot;modal transition&quot;&gt;&lt;/p&gt;
&lt;p&gt;There we have it, we now have an overly simplified way of presenting a modal view full screen using an animation that comes up from the bottom of the screen and disappears in the same mannor.&lt;/p&gt;
&lt;p&gt;We are able to see that it&#39;s very easy to add a delightful animation and transition to the presentation of a view. From here, we hope you go and create some amazingly beautiful products that bring a lot of delight to users. If you&#39;re after help with that, please get in touch using the social links at the bottom of this page.&lt;/p&gt;
</description>
      <pubDate>Sat, 07 Mar 2020 07:00:00 GMT</pubDate>
      <dc:creator>Delightful Apps</dc:creator>
      <guid>https://delightfulapps.co/blog/transitions-in-swiftui/</guid>
    </item>
  </channel>
</rss>