<?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:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Carlos Mbendera on Medium]]></title>
        <description><![CDATA[Stories by Carlos Mbendera on Medium]]></description>
        <link>https://medium.com/@carlosmbe?source=rss-8e2b35a4a719------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*zYHTktsmKYSGeLElAJlmyg.jpeg</url>
            <title>Stories by Carlos Mbendera on Medium</title>
            <link>https://medium.com/@carlosmbe?source=rss-8e2b35a4a719------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 06 Jun 2026 22:47:33 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@carlosmbe/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[How To Make Better iOS Apps: OnBoarding]]></title>
            <link>https://carlosmbe.medium.com/how-to-make-better-ios-apps-onboarding-2246a82be611?source=rss-8e2b35a4a719------2</link>
            <guid isPermaLink="false">https://medium.com/p/2246a82be611</guid>
            <category><![CDATA[user-interface]]></category>
            <category><![CDATA[user-experience]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Carlos Mbendera]]></dc:creator>
            <pubDate>Fri, 05 Jun 2026 23:31:00 GMT</pubDate>
            <atom:updated>2026-06-05T23:31:00.628Z</atom:updated>
            <content:encoded><![CDATA[<p><strong><em>An Analysis of </em></strong><a href="https://www.atc.com"><strong><em>ATC — Live Air Traffic Control</em></strong></a></p><p>I had the pleasure of chatting with the CEO of <a href="https://www.atc.com">ATC</a> / Enhanced Radar recently. They built an iOS app that lets you listen to live and past Air Traffic Control audio. It’s been charting well in the Travel section of the US App Store.</p><p>The app is really cool. But the onboarding bothered me. So here are my thoughts on how to improve it. I’m still learning and these are my humble opinions.</p><h3>Steal Like An Artist</h3><p>Before critiquing any app, it’s nice to get a feel for the space first. What does world class look like? Who’s the niche cult favorite? What’s the design language? Basically what does good even mean.</p><p>For ATC, I looked at other apps charting in Travel with a focus on planes and Air Traffic Radio apps. That led me to four:</p><ol><li>Flighty (Ranked #87 at the time)</li><li>Flightradar24 (Ranked #45 at the time)</li><li>FlightAware (Ranked #126 at the time)</li><li>PlaneFinder</li></ol><p>Flighty was my north star. Cult following, Apple Design Award, pretty and easy to use.</p><p>The other three were interesting in their own right. Great reviews, press, and real value for users. They all did well enough to rank well, but I felt like their designs were a product of 2012 that no one has touched since. Yet, users didn’t seem to mind.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*La8BOj_GrqQNFMv9DHQ32w.png" /></figure><h3>Findings</h3><p>Anyways, after studying those apps, I had data to back up my feeling about ATC’s onboarding. It felt long. The app itself has a really cool and unique opening with live recordings playing and dropping you into a demo. That part feels great and fun. The gap between that and the paywall is my gripe.</p><p><strong>Onboarding Screen Counts</strong></p><ol><li>Flighty = 0</li><li>Flightradar24 = 2 (Privacy + Paywall)</li><li>FlightAware = 4 (Privacy + Paywall + Tracking + Account)</li><li>PlaneFinder = 4 (Marketing + Notifications + Tracking + Paywall)</li><li>ATC = 20 (Demo + Notifications + Tracking + Paywall + Data Collection)</li></ol><p>Based on the numbers, a good average is around three or four onboarding screens.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vSma-I7MqQw1sqoYwo3hVQ.png" /></figure><h3>What I would change</h3><p>I think ATC can collect the same data it already collects and present it more concisely.</p><p>The biggest issue is that the app is asking users to personalize the app before they’ve even tried it. This makes more sense after the paywall as the narrative shifts from “<em>I downloaded this and now have to configure it</em>” to “<em>I own this and want to make it mine.</em>” So I’d pull personalization out of onboarding entirely for ATC specifically.</p><p>Onboarding should not punish users for wanting to use the app. Half interested users should be able to judge your work on its merits, not on the front door.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wFvJFVUVJ9AO31WEhaziXA.png" /></figure><p>Moreover, I’d combine the tracking and notification prompts into one screen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hOSjmVZNNz76iFJqhQh8ag.png" /></figure><p>Those two changes take ATC from 20 screens to just 4 screens.</p><p>Two of the remaining four are already hero moments that could be made even cooler. I don’t want to explain those flows in an article so I’d recommend you download the app and try it with the context of what I’ve written below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fj5S_p9W0RtVQZAnX2y9Ag.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dZH1QbPiH8KgPjoLdqz6KQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9unqTF-_IJRl4OtFsMpJZA.png" /></figure><h3>Put It All Together</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BvBxG2e66ZYp4Z8AcL6k8w.png" /></figure><p>The app’s core idea is great, the opening demo is fun, and the modern UI for Air Traffic Control radio is appreciated, even for a non pilot like myself. The onboarding is just a cover for a book that doesn’t match its content. As of June 5, 2026, they’re also hiring for an iOS dev role if that’s your thing. Just email them or apply from the app.</p><p>Also, I have a <a href="https://carlosmbe.substack.com">Substack</a> now. Where I write about my experience building <a href="https://carlosmbe.com/setto">Setto</a> and other cool things. <a href="https://carlosmbe.com/setto">Setto</a> is basically tinder for music recommendations. You pick a few artists, swipe on songs and the DJ figures out what you’re into. Last I checked, it was on the US Best New Apps list and Canada’s Hot This Week for both iPad and iPhone.</p><p><a href="https://carlosmbe.substack.com/">carlosmbe.substack.com</a></p><p>If you’d like me to analyze your app, share some advice or write about a specific topic. Send me an email or comment on this post. My details and calendar are on <a href="http://carlosmbe.com/">carlosmbe.com</a>.</p><p>In case I don’t see you again, good afternoon, good evening, and good night.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2246a82be611" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How To Use the Object Capture Framework in SwiftUI for iOS Development]]></title>
            <link>https://carlosmbe.medium.com/how-to-use-the-object-capture-framework-in-swiftui-for-ios-development-257bebba725e?source=rss-8e2b35a4a719------2</link>
            <guid isPermaLink="false">https://medium.com/p/257bebba725e</guid>
            <category><![CDATA[computer-vision]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[3d-modeling]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Carlos Mbendera]]></dc:creator>
            <pubDate>Thu, 25 Sep 2025 17:31:47 GMT</pubDate>
            <atom:updated>2025-09-25T17:31:47.017Z</atom:updated>
            <content:encoded><![CDATA[<h3>How To Use The Object Capture Framework in SwiftUI for iOS Development</h3><p>Hello There! Ever wanted to quickly create 3D Models of Objects in your room using only the iPhone (or iPad) camera? If the answer is yes, then this is the article you’ve been looking for.</p><p>Quick note, this is an update of <a href="https://carlosmbe.hashnode.dev/how-to-use-3d-object-capture-in-swiftui">an article I wrote on my personal Hashnode</a> Blog. I’ve decided to cross publish it here on Medium due to popular demand.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gavjJuexBZvx4rHb" /><figcaption>Photo by <a href="https://unsplash.com/@jorgefdezsalas?utm_source=medium&amp;utm_medium=referral">Jorge Fernández Salas</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Alright, so what is Object Capture? In the olden days, also known as WWDC23, Apple released and announced a new feature in Reality Kit. Object Capture. It allows developers to create AR Experinces using 3D Models that users can create with their iPhone.</p><p>In other words, instead of using Blender or other 3D Workstations, we can now create objects for rendering in AR or VR or other Computer Graphics pipelines with the iPhone camera. Pretty neat for the “era of Spatial Computing.”</p><p>One approach to do this is taking multiple pictures of the object we’re scanning and use the Lidar sensor for depth. This graphic from the Apple Documentation illustrates it well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XItxIdvaatitk2CCHxC8eg.png" /><figcaption>Image from Apple<a href="https://developer.apple.com/documentation/realitykit/capturing-photographs-for-realitykit-object-capture"> Object Capture Docs</a></figcaption></figure><p>Alternatively, you can also put the object on a turn table.</p><p>Anyways, now that you know the vision. Let’s talk about code.</p><h3>Introduction</h3><p>Here’s a <strong>smooth</strong> <a href="https://carlosmbe.hashnode.dev/how-to-use-3d-object-capture-in-swiftui#heading-target-functionality">demo video of the final implementation</a>. The gif below is just for illsutration.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/406/1*lGOcLKyO0Zcl7PI0Wap9lA.gif" /></figure><p>To implement Object Capture in your app, target devices must be an iPhone or iPad with the following:</p><ul><li>A LiDAR Scanner</li><li>An A14 Bionic chip or later</li><li>iOS or iPadOS 18 or later</li></ul><h3>References</h3><p>When building my implementation, I referenced the following articles, projects and videos.</p><p><a href="https://github.com/littleossa/SuperSimpleObjectCapture">SuperSimpleObjectCapture</a></p><p><a href="https://dev.classmethod.jp/articles/ios17-on-device-object-capture/">Japanese Article Breaking Down Super Simple Object Capture</a></p><p><a href="https://developer.apple.com/videos/play/wwdc2023/10191/">WWDC23: Meet Object Capture for iOS</a></p><p>Apple Official Sample Project — <a href="https://developer.apple.com/documentation/realitykit/scanning-objects-using-object-capture#Configure-the-sample-code-project">Scanning objects using Object Capture</a>, <a href="https://developer.apple.com/documentation/realitykit/capturing-photographs-for-realitykit-object-capture">Capturing photographs for RealityKit Object Capture</a></p><h3>Show Me The Code!</h3><p>Firstly, we need to request access to the user’s Camera before we can start scanning objects. To do this, we want to navigate to our Target’s Settings in Xcode.</p><p>Here’s <a href="https://developer.apple.com/documentation/avfoundation/requesting-authorization-to-capture-and-save-media">Apple’s guide on doing that</a>.</p><ol><li>Navigate to your Project’s target</li><li>Go to the Target’s Info</li><li>Add Privacy - Camera Usage Description and write a description for it</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dbQ8MWsvz19TkxUFfUx_bw.png" /><figcaption>Project’s Privacy Plist</figcaption></figure><p>Awesome!</p><p>Now we’ll create a few Swift files and I’ll highlight what each file does and how you can modify it for your use case. I’m using the <a href="https://github.com/littleossa/SuperSimpleObjectCapture">SuperSimpleObjectCapture</a>’s project structure as that’s the most malleable. If you want something that’s a better reference for shipping code, use Apple’s project that can be found above.</p><h3>ScanningView.swift</h3><p>This is the entry point for our scanning logic. In the SuperSimpleObjectCapture, it’s the Content View. I’ve refactored the code a bit to make it a bit more compacted. You should get an error regarding the <strong>CreateButton</strong> and <strong>ARQuickLookView</strong>. But we’ll implement those in due time.</p><pre>//Shoutout to the SimpleObjectCapture project crated by @littleossa<br>  import SwiftUI<br>  import RealityKit<br><br>  struct ScanView: View {<br><br>      @State private var session: ObjectCaptureSession?<br>      @State private var imageFolderPath: URL?<br>      @State private var photogrammetrySession: PhotogrammetrySession?<br>      @State private var modelFolderPath: URL?<br>      @State private var isProgressing = false<br>      @State private var quickLookIsPresented = false<br><br>      var modelPath: URL? {<br>          return modelFolderPath?.appending(path: &quot;model.usdz&quot;)<br>      }<br><br>      var body: some View {<br><br>          ZStack(alignment: .bottom) {<br>              if let session {<br>                  ObjectCaptureView(session: session)<br><br>                  VStack(spacing: 16) {<br><br>                      if session.state == .ready || session.state == .detecting {<br>                          // Detect and Capture<br>                          CreateButton(session: session)<br>                      }<br><br>                      HStack {<br>                          Text(session.state.label)<br>                              .bold()<br>                              .foregroundStyle(.yellow)<br>                              .padding(.bottom)<br>                      }<br><br>                  }<br>              }<br><br>              if isProgressing {<br>                  //I&#39;d suggest creating a view that shows the user that the model is processing in a more straight forward<br>                  Color.blue.opacity(0.4)<br>                      .overlay {<br>                          VStack {<br>                              Text(&quot;Processing Model&quot;)<br>                              ProgressView()<br>                          }<br>                      }<br>              }<br><br><br>          }<br>          .task {<br>              guard let directory = createNewScanDirectory()<br>              else { return }<br>              session = ObjectCaptureSession()<br><br>              modelFolderPath = directory.appending(path: &quot;Models/&quot;)<br>              imageFolderPath = directory.appending(path: &quot;Images/&quot;)<br>              guard let imageFolderPath else { return }<br>              session?.start(imagesDirectory: imageFolderPath)<br>          }<br>          .onChange(of: session?.userCompletedScanPass) { _, newValue in<br>              if let newValue,<br>                 newValue {<br>                  //MARK: Take Note of this dear reader<br>                  // This time, I&#39;ve completed one scan pass.<br>                  // However, Apple recommends that the scan pass should be done three times.<br>                  session?.finish()<br>              }<br>          }<br>          .onChange(of: session?.state) { _, newValue in<br>              if newValue == .completed {<br>                  session = nil<br><br>                  Task {<br>                      await startReconstruction()<br>                  }<br>              }<br>          }<br>          .sheet(isPresented: $quickLookIsPresented) {<br><br>              if let modelPath {<br>                  ARQuickLookView(modelFile: modelPath) {<br>                      guard let directory = createNewScanDirectory()<br>                      else { return }<br>                      quickLookIsPresented = false<br>                      // TODO: Restart ObjectCapture<br>                  }<br>              }<br>          }<br>      }<br>          func createNewScanDirectory() -&gt; URL? {<br>              guard let capturesFolder = getRootScansFolder()<br>              else { return nil }<br><br>              let formatter = ISO8601DateFormatter()<br>              let timestamp = formatter.string(from: Date())<br>              let newCaptureDirectory = capturesFolder.appendingPathComponent(timestamp,<br>                                                                              isDirectory: true)<br>              print(&quot;Start creating capture path: \(newCaptureDirectory)&quot;)<br>              let capturePath = newCaptureDirectory.path<br>              do {<br>                  try FileManager.default.createDirectory(atPath: capturePath,<br>                                                          withIntermediateDirectories: true)<br>              } catch {<br>                  print(&quot;Failed to create capture path: \(capturePath) with error: \(String(describing: error))&quot;)<br>              }<br><br>              var isDirectory: ObjCBool = false<br>              let exists = FileManager.default.fileExists(atPath: capturePath,<br>                                                          isDirectory: &amp;isDirectory)<br>              guard exists, isDirectory.boolValue<br>              else { return nil }<br>              print(&quot;New capture path was created&quot;)<br>              return newCaptureDirectory<br>          }<br><br>          func getRootScansFolder() -&gt; URL? {<br>              guard let documentFolder = try? FileManager.default.url(for: .documentDirectory,<br>                                                                      in: .userDomainMask,<br>                                                                      appropriateFor: nil,<br>                                                                      create: false)<br>              else { return nil }<br>              return documentFolder.appendingPathComponent(&quot;Scans/&quot;, isDirectory: true)<br>          }<br><br>          func startReconstruction() async {<br>              guard let imageFolderPath,<br>                    let modelPath else { return }<br>              isProgressing = true<br>              do {<br>                  photogrammetrySession = try PhotogrammetrySession(input: imageFolderPath)<br>                  guard let photogrammetrySession else { return }<br>                  try photogrammetrySession.process(requests: [.modelFile(url: modelPath)])<br>                  for try await output in photogrammetrySession.outputs {<br>                      switch output {<br>                      case .requestError, .processingCancelled:<br>                          isProgressing = false<br>                          self.photogrammetrySession = nil<br>                          // TODO: Restart ObjectCapture<br>                      case .processingComplete:<br>                          isProgressing = false<br>                          self.photogrammetrySession = nil<br>                          quickLookIsPresented = true<br>                      default:<br>                          break<br>                      }<br>                  }<br><br>              } catch {<br>                  print(&quot;Error!!!&quot;, error)<br>              }<br>          }<br><br>  }</pre><p>Awesome! At this point, if you try building the app it will complain about <strong>ARQuickLookView </strong>and <strong>CreateButton</strong> in the Scan View. Let’s sort that out</p><h3>ARQuickLookView.swift</h3><p>So this view uses the QuickLook library from Apple and is called after we’re done creating a 3D Model in order to preview the result.</p><p>You may be tempted to rewrite this code for a custom User Interface to handle your model file (at least I was) but that might not be the best approach. In true SwiftUI fashion, this view works very well for what it was designed for. Modifying it will be very sad and painful.</p><p>Thus, what I opted to do is focus on how I save and manage the file produced. Please drop some suggestions in the comments, I would greatly appreciate them.</p><pre>//Shoutout to the SimpleObjectCapture project crated by @littleossa<br>  import SwiftUI<br>  import QuickLook<br><br>  struct ARQuickLookView: UIViewControllerRepresentable {<br><br>      let modelFile: URL<br>      let endCaptureCallback: () -&gt; Void<br><br>      func makeUIViewController(context: Context) -&gt; QLPreviewControllerWrapper {<br>          let controller = QLPreviewControllerWrapper()<br>          controller.previewController.dataSource = context.coordinator<br>          controller.previewController.delegate = context.coordinator<br>          return controller<br>      }<br><br>      func updateUIViewController(_ uiViewController: QLPreviewControllerWrapper, context: Context) {}<br><br>      func makeCoordinator() -&gt; Coordinator {<br>          return Coordinator(parent: self)<br>      }<br><br>      class Coordinator: NSObject, QLPreviewControllerDelegate, QLPreviewControllerDataSource {<br>          let parent: ARQuickLookView<br><br>          init(parent: ARQuickLookView) {<br>              self.parent = parent<br>          }<br><br>          func numberOfPreviewItems(in controller: QLPreviewController) -&gt; Int {<br>              return 1<br>          }<br><br>          func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -&gt; QLPreviewItem {<br>              return parent.modelFile as QLPreviewItem<br>          }<br><br>          func previewControllerWillDismiss(_ controller: QLPreviewController) {<br>              parent.endCaptureCallback()<br>          }<br>      }<br>  }<br><br>  extension ARQuickLookView {<br><br>      class QLPreviewControllerWrapper: UIViewController {<br>          let previewController = QLPreviewController()<br>          var quickLookIsPresented = false<br><br>          override func viewDidAppear(_ animated: Bool) {<br>              super.viewDidAppear(animated)<br><br>              if !quickLookIsPresented {<br>                  present(previewController, animated: false)<br>                  quickLookIsPresented = true<br>              }<br>          }<br>      }<br>  }</pre><h3>CreateButton.swift</h3><pre> // You can honestly create your own Create Button, this is the one from the SimpleObjectCapture project.<br>  // Estenially the label for the create button changes depending on the state of the project<br>  // And where we are in the Object Capture pipeline<br><br>  import SwiftUI<br>  import RealityKit<br><br>  @MainActor<br>  struct CreateButton: View {<br>      let session: ObjectCaptureSession<br><br>      var body: some View {<br>          Button(action: {<br>              performAction()<br>          }, label: {<br>              Text(label)<br>                  .foregroundStyle(.white)<br>                  .padding()<br>                  .background(.tint)<br>                  .clipShape(Capsule())<br>          })<br>      }<br><br>      private var label: LocalizedStringKey {<br>          if session.state == .ready {<br>              return &quot;Start Detecting&quot;<br>          } else if session.state == .detecting {<br>              return &quot;Start Capturing&quot;<br>          } else {<br>              return &quot;Undefined&quot;<br>          }<br>      }<br><br>      private func performAction() {<br>          if session.state == .ready {<br>              let isDetecting = session.startDetecting()<br>              print(isDetecting ? &quot;Start Detecting&quot; : &quot;Did Not Start Detecting Session&quot;)<br>          } else if session.state == .detecting {<br>              session.startCapturing()<br>          } else {<br>              print(&quot;Undefined&quot;)<br>          }<br>      }<br>  }<br><br><br>  extension ObjectCaptureSession.CaptureState {<br><br>      var label: String {<br>          switch self {<br>          case .initializing:<br>              &quot;Initializing&quot;<br>          case .ready:<br>              &quot;Ready&quot;<br>          case .detecting:<br>              &quot;Detecting&quot;<br>          case .capturing:<br>              &quot;Capturing&quot;<br>          case .finishing:<br>              &quot;Finishing&quot;<br>          case .completed:<br>              &quot;Completed&quot;<br>          case .failed(let error):<br>              &quot;Failed with Error: \(String(describing: error))&quot;<br>          @unknown default:<br>              fatalError(&quot;Unknown Default: \(self)&quot;)<br>          }<br>      }<br>  }</pre><h3>Conclusion</h3><p>That’s it, with 3 Swift Files you can now scan and create 3D Models in your iOS Apps. Congratulations! So the user expereince is as follows:</p><ol><li>User opens the app and grants Camera permission</li><li>They click the start detecting button when their object is in the viewfinder</li><li>They confirm the detected proportions</li><li>They scan the 3D Object</li><li>The model is generated and can be previewed with QuickLook</li></ol><p>Please note that this app is a starter and Apple has some other things they’d recommend you implement with this framework, such as having 3 iterations of the scans. Here’s their relevant project.</p><p><a href="https://developer.apple.com/videos/play/wwdc2023/10191/">WWDC23: Meet Object Capture for iOS</a></p><p>Apple Official Sample Project — <a href="https://developer.apple.com/documentation/realitykit/scanning-objects-using-object-capture#Configure-the-sample-code-project">Scanning objects using Object Capture</a></p><p>Thanks for reading. I really appreciate it and hope that your project compiles bug-free.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/498/0*mdIQr1WSh244I0zz" /><figcaption>&lt; Me being appreciative&gt; Gif from Samurai Champloo</figcaption></figure><p>Hope you have a nice one! And in case I don’t see you again. Good afternoon, good evening and good night!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=257bebba725e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Running Speech Models with Swift Using Sherpa-Onnx for Apple Development]]></title>
            <link>https://carlosmbe.medium.com/running-speech-models-with-swift-using-sherpa-onnx-for-apple-development-d31fdbd0898f?source=rss-8e2b35a4a719------2</link>
            <guid isPermaLink="false">https://medium.com/p/d31fdbd0898f</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[speech-recognition]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Carlos Mbendera]]></dc:creator>
            <pubDate>Wed, 09 Apr 2025 20:31:40 GMT</pubDate>
            <atom:updated>2025-09-26T20:14:32.257Z</atom:updated>
            <content:encoded><![CDATA[<p>I love music and anything related to audio. I also enjoy writing code in Swift with SwiftUI. I was recently working on a iOS project that uses Speech Diarization and wanted an on-device model.</p><p>In this article, I’ll be sharing what I learned, break down how I set up my project, a few challenges I faced along the way and more.</p><p>I also have a <strong>new blog on Hashnode</strong>, where I’ll be writing about more cool tech, so check that out here: <a href="https://carlosmbe.hashnode.dev/">https://carlosmbe.hashnode.dev/</a>.</p><p>An <strong>updated version of this article</strong> is also on Hashnode: <a href="https://carlosmbe.hashnode.dev/running-speech-models-with-swift-using-sherpa-onnx-for-apple-development">https://carlosmbe.hashnode.dev/running-speech-models-with-swift-using-sherpa-onnx-for-apple-development</a></p><p><strong>Update from September 2025: </strong>There now exists an open source library that makes Speech Diarization easy to implement across the Apple ecosystem called <a href="https://github.com/FluidInference/FluidAudio">FluidAudio</a>. For most people, I’d recommend using that. However, its essentially an abstraction of the Sherpa code. Hence, if you’re doing some advanced or custom work, this article may still be relevant as you have direct access to the C/C++/Swift code.</p><p>Feel free to share any tips and suggestions you may have as I’m still learning.</p><blockquote>Speech diarization is the process of segmenting and labeling an audio stream by speaker to identify &quot;who spoke when,&quot; enabling a clear understanding of who is speaking at any given time.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-9Kgsl23rw4QXovS" /><figcaption>Photo by <a href="https://unsplash.com/@kellysikkema?utm_source=medium&amp;utm_medium=referral">Kelly Sikkema</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Part 1: Research and the Motive Behind my Approach</h3><p>After numerous hours of Googling, I found a promising model, <a href="https://github.com/pyannote/pyannote-audio"><strong><em>Pyannote’s Speaker Diarization 3.1</em></strong></a><strong><em>.</em></strong></p><p>It works well.</p><p>If you’re planning to have a python server run your models, then I’d probably suggest using that as it’ll save you a fair amount of work.</p><p>What work? Well, my objective was to have an on-device speech diarization, thus, I needed to convert this model to a format my Xcode project would recognize.</p><p>My first instinct was to <a href="https://medium.com/codex/how-to-convert-tensorflow-h5-models-to-core-ml-70a28bbd5c60">use CoreMLTools</a> to convert the model. Unfortunately, this didn’t work. If you attempt to retrace my debugging steps, you shall eventually find yourself at <a href="https://github.com/apple/coremltools/issues/1753">certain GitHub Issue</a> in CoreMLTools and realize that you’re repeating the journey of a few developers before you.</p><p>The alternative? Use the C++ version and have a bridging header for your Swift and C++ code. This may sound intimidating, especially if you’re a native Swift developer who’s never worked with C++.</p><p>Don’t worry, I’m here to make this as easy as possible.</p><h3>Part 2: Sherpa-Onnx</h3><p>By using <a href="https://github.com/k2-fsa/sherpa-onnx">Sherpa-Onnx</a> we can bypass a significant amount of boilerplate and porting.</p><p>They even have starter projects you can use as a reference for how to structure your project.</p><p>However, I did find it a bit overwhelming and created <a href="https://github.com/carlosmbe/SpeechDiarizationStarter">my own Starter Project</a>. It’s oriented towards Speech Diarization but you can use it for Sherpa-Onnx’s other models, such as voice activity detection, audio tagging and more.</p><p>If you don’t want to clone my Starter Project, you can follow the Sherpa-Onnx build instructions and compile the framework by yourself. Just clone <a href="https://github.com/k2-fsa/sherpa-onnx">k2-fsa/sherpa-onnx</a> and follow these <a href="https://k2-fsa.github.io/sherpa/onnx/ios/build-sherpa-onnx-swift.html">build instructions</a></p><p><strong>Please note, </strong>if you’re aiming to build for macOS, it has its own seprate build.sh file and that visionOS’s build script doesn’t exist (officially).</p><p>I built <a href="https://github.com/k2-fsa/sherpa-onnx/issues/1946#issuecomment-2699537872">a branch of Sherpa-Onnx that supports the VisionOS Simulator</a> and should theoretically support the device with a few line changes.</p><h3>How Do I build Sherpa-Onnx?</h3><p>Alright, now that we’ve covered the motivation for my approach and I’ve disclosed any potential issues. Let me walk through the technicals, starting with building the Frameworks.</p><h4>Building the Frameworks for iOS</h4><ol><li>Clone Sherpa-Onnx</li></ol><pre>git clone https://github.com/k2-fsa/sherpa-onnx.git</pre><p>2. Run the Build Script (Either the iOS, macOS or visionOS script)</p><pre>./build-ios.sh</pre><p>It will download Onnx Runtime and build Sherpa-Onnx for iOS. You should see a new folder titled build-ios</p><p>3. Either use:</p><ul><li><a href="https://github.com/carlosmbe/SpeechDiarizationStarter">My Starter Project</a></li><li>The Sherpa-Onnx examples found in folders <a href="https://github.com/k2-fsa/sherpa-onnx/tree/master/ios-swift">ios-swift</a> and <a href="https://github.com/k2-fsa/sherpa-onnx/tree/master/ios-swiftui">ios-swiftui</a></li><li>Create a new project and the copy the frameworks ios-onnxruntime and sherpa-onnx.xcframework to your project from the newly created build-ios folder, create a bridging header and copy the Sherpa-Onnx Swift functions to your new project.</li></ul><p>If you used the Starter Project or the Sherpa examples. You should be good to download Models and just experiment.</p><p>However, if you’re opting to create your own project from scratch, we have to a few more steps to follow. In particular, we need to set up a Bridging Header and add some linker flags.</p><p>Essentially its some boiler plate code that allows our Swift and C++ files to communicate. If you’ve worked with Metal, then this should feel like home.</p><p>I’ll write another article in the future documenting this process as its fairly involved.</p><h3>Using My Starter Project for Speech Diarization</h3><p>If you opt to use <a href="https://github.com/carlosmbe/SpeechDiarizationStarter">My Starter Project</a>, you can easily start performing Speech Diarization on clips as soon as you download and add the Onnx-Runtime framework as highlighted in the ReadMe. To test it out, just add a file titled “Clip.mp4” and run the app.</p><h3>Show Me The Code</h3><p>Here’s a breakdown of what’s happening in the background.</p><ol><li>Firstly, using AudioKit, we convert the clip to a format the Model accepts. Specifically a 16 kHz, mono, 32‑bit float WAV file. If you want your app size to be smaller and rely on less frameworks, you could probably do this with Apple’s built in AVFoundation. AudioKit abstracts a lot of the code.</li><li>Afterwards, we pass the newly converted file to my <strong><em>runDiarization</em></strong> function. This is where all the C++ and Swift interactions occur. Sherpa-Onnx also abstracts this so it looks somewhat Swifty, but be fully aware that pointers are being passed around in the background.</li></ol><ul><li>The <strong><em>runDiarization </em></strong>function expects a requires that we have a Speech Diarization model in the project alongside an embedding Extractor Model. The Starter Project has these preloaded, so we’re mainly creating a config for the model. So we pass an expected number of speakers.</li><li>This is the ViewModel with all of this code:</li></ul><pre><br>import AVFoundation<br>import Foundation<br><br>class SDViewModel : ObservableObject{<br>    <br>    let segmentationModel = getResource(&quot;SpeechModel&quot;, &quot;onnx&quot;)<br>    let embeddingExtractorModel = getResource(&quot;3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k&quot;, &quot;onnx&quot;)<br> <br>    <br>    func runDiarization(waveFileName: String, numSpeakers: Int = 0, fullPath: URL? = nil)  async -&gt; [SherpaOnnxOfflineSpeakerDiarizationSegmentWrapper] {<br>       <br>        let waveFilePath = fullPath?.path ?? getResource(waveFileName, &quot;wav&quot;)<br>        <br>        var config = sherpaOnnxOfflineSpeakerDiarizationConfig(<br>                segmentation: sherpaOnnxOfflineSpeakerSegmentationModelConfig(<br>                pyannote: sherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig(model: segmentationModel)),<br>                embedding: sherpaOnnxSpeakerEmbeddingExtractorConfig(model: embeddingExtractorModel),<br>                clustering: sherpaOnnxFastClusteringConfig(numClusters: numSpeakers)<br>        )<br>        <br>        //The SherpaOnnx functions are defined in SherpaFiles/Swift/SherpaOnnx.swift<br>        let sd = SherpaOnnxOfflineSpeakerDiarizationWrapper(config: &amp;config)<br><br>        let fileURL: NSURL = NSURL(fileURLWithPath: waveFilePath)<br>        let audioFile = try! AVAudioFile(forReading: fileURL as URL)<br><br>        let audioFormat = audioFile.processingFormat<br>        assert(Int(audioFormat.sampleRate) == sd.sampleRate)<br>        assert(audioFormat.channelCount == 1)<br>        assert(audioFormat.commonFormat == AVAudioCommonFormat.pcmFormatFloat32)<br><br>        let audioFrameCount = UInt32(audioFile.length)<br>        let audioFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount)<br><br>        try! audioFile.read(into: audioFileBuffer!)<br>        let array: [Float]! = audioFileBuffer?.array()<br>        <br>        let segments = await Task.detached(priority: .background) {<br>               return sd.process(samples: array)<br>           }.value<br>        <br>        for i in 0..&lt;segments.count {<br>          print(&quot;\(segments[i].start) -- \(segments[i].end) speaker_\(segments[i].speaker)&quot;)<br>        }<br>        <br>        return segments<br>    }<br>    <br>}</pre><ul><li>We also have a few extensions to make accessing the Audio File easier.</li></ul><pre>import AVFoundation<br><br>extension AudioBuffer {<br>    func array() -&gt; [Float] {<br>        return Array(UnsafeBufferPointer(self))<br>    }<br>}<br><br>extension AVAudioPCMBuffer {<br>    func array() -&gt; [Float] {<br>        return self.audioBufferList.pointee.mBuffers.array()<br>    }<br>}</pre><p>For reference, the Sherpa-Onnx official examples have a get resource function and this is its implementation.</p><pre>import Foundation<br><br>func getResource(_ forResource: String, _ ofType: String) -&gt; String {<br>  let path = Bundle.main.path(forResource: forResource, ofType: ofType)<br>  precondition(<br>    path != nil,<br>    &quot;\(forResource).\(ofType) does not exist!\n&quot; + &quot;Remember to change \n&quot;<br>      + &quot;  Build Phases -&gt; Copy Bundle Resources\n&quot; + &quot;to add it!&quot;<br>  )<br>  return path!<br>}</pre><p>If you’re using the starter projcet as a reference for your own custom project. Please take note of my Build Settings. Specifically:</p><ul><li><strong>Swift Compiler — General</strong>: Objective C Bridging Header</li><li>Search P<strong>aths</strong>: Header Search Paths</li><li><strong>Linking — General</strong>: Other Linking Flags: Set to -lc++</li></ul><p><strong>Pretty Cool Links:</strong></p><ul><li>Check out <a href="https://github.com/carlosmbe">my GitHub</a> for some other cool projects, I’ve built.</li><li>Connect with me on <a href="https://www.linkedin.com/in/carlosmbe/?_l=en_US">LinkedIn</a>.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d31fdbd0898f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Swift x Metal for 3D Graphics Rendering Part 5: Drawing 3D Models from Files]]></title>
            <link>https://carlosmbe.medium.com/swift-x-metal-for-3d-graphics-rendering-part-5-drawing-3d-models-from-files-27c9cfd288c2?source=rss-8e2b35a4a719------2</link>
            <guid isPermaLink="false">https://medium.com/p/27c9cfd288c2</guid>
            <category><![CDATA[computer-graphics]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[3d-rendering]]></category>
            <category><![CDATA[metal]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Carlos Mbendera]]></dc:creator>
            <pubDate>Fri, 31 Jan 2025 21:16:48 GMT</pubDate>
            <atom:updated>2025-01-31T21:16:48.866Z</atom:updated>
            <content:encoded><![CDATA[<p>Drawing Cubes and other 3D Shapes is fun and all, but once we start rendering models made by other people. The range of things we can build expands drastically almost overnight.</p><p>Very Important shoutout to Koedeco and Caroline Begbie. They have an awesome article talking about this. <a href="https://www.kodeco.com/books/metal-by-tutorials/v2.0/chapters/2-3d-models">Link To Article</a>.</p><p>Most of the work we’ll do in this part is refactoring our logic from part 4 so that its a bit more open ended and capable of rendering data from files containing 3D Models such as but not limited to .obj and .usdz files.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/606/1*vW0FgEW7FilBR3b8r5sHgg.jpeg" /><figcaption>From Post by ARasDeFiga in Reddit Forum r/catloaf</figcaption></figure><h3>Show Me The Code</h3><p>Firstly, we need to be able to read a .obj file loaded into the app. To do this, we can add some logic in our Renderer’s init function that loads our file as a MDL Asset.</p><pre>class Renderer : NSObject, MTKViewDelegate {<br> //... Old Properties ..<br>    <br>    //New Properties<br>    let allocator : MTKMeshBufferAllocator<br>    let asset : MDLAsset<br>    let mdlMesh : MDLMesh<br>    let mesh : MTKMesh<br><br>    init(_ parent : ContentView) {<br>//... Old Code ...<br>  //New logic that intitalises our new properties<br>        allocator = MTKMeshBufferAllocator(device: device)<br>        <br>        asset = MDLAsset(url: Bundle.main.url(forResource: &quot;Your File Here&quot;, withExtension: &quot;obj&quot;)!,<br>                         vertexDescriptor: .defaultLayout,<br>                         bufferAllocator: allocator)<br>        <br>        mdlMesh = asset.childObjects(of: MDLMesh.self).first as! MDLMesh<br>        <br>        mesh = try! MTKMesh(mesh: mdlMesh, device: device)<br><br>        super.init()<br>    }</pre><p>If you need a file to test out your new rendering pipeline. You can look up “Free 3D Models” on Google and download a .obj file or anything really.</p><p>Before we modify our draw call. I would like to refactor and simplify our shaders to better align with the one used in Kodeco’s content. The content I wrote in <a href="https://carlosmbe.medium.com/swift-x-metal-for-3d-graphics-rendering-part-3-vertex-and-index-buffers-56a1d155f316">Part 3 </a>still applies. We’re just changing our struct definitions in the bridge and making our Vertex only handle positions and temporarily removing the color component.</p><pre>#include &quot;bridge.h&quot;<br>#include &lt;metal_stdlib&gt;<br>using namespace metal;<br><br>vertex VertexOut vertexMain(<br>                            VertexIn v [[stage_in]],<br>                            constant Uniforms &amp;uniforms[[buffer(2)]])<br>{<br>    VertexOut data;<br>    float4 position = uniforms.projectionMatrix * uniforms.viewMatrix<br>    * uniforms.modelMatrix * v.position;<br>    data.position = position;<br>    return data;<br>};<br><br>half4 fragment fragmentMain(){<br>    return half4(1, 1, 1.0, 1);<br>};<br><br></pre><p>In bridge.h, we have some new definitions.</p><pre>#ifndef bridge_h<br>#define bridge_h<br>#include &lt;simd/simd.h&gt;<br><br>struct Vertex{<br>    vector_float4 position;<br>    vector_float3 color;<br>};<br><br>struct VertexIn {<br>    vector_float4  position [[attribute(0)]];<br>};<br><br>struct VertexOut {<br>    vector_float4  position [[position]];<br>};<br><br><br>struct Uniforms{<br>  matrix_float4x4 modelMatrix;<br>  matrix_float4x4 viewMatrix;<br>  matrix_float4x4 projectionMatrix;<br>};<br><br>#endif /* bridge_h */</pre><p>Ok great. We’re almost done. I promise. Now we need to make one change before we call our draw call. We’re going to add a <strong>vertexDescriptor </strong>to our pipeline builder. They’re two ways we could do this. We can create one manually or add an extension to handle this. In this case, I am going to use an extension written by Caroline Bagbie from Kodeco.</p><pre>//Extensions. You can put these wherever you feel fit<br><br>extension MTLVertexDescriptor {<br>  static var defaultLayout: MTLVertexDescriptor? {<br>    MTKMetalVertexDescriptorFromModelIO(.defaultLayout)<br>  }<br>}<br><br>extension MDLVertexDescriptor {<br>  static var defaultLayout: MDLVertexDescriptor {<br>    let vertexDescriptor = MDLVertexDescriptor()<br>    var offset = 0<br>    vertexDescriptor.attributes[0] = MDLVertexAttribute(<br>      name: MDLVertexAttributePosition,<br>      format: .float3,<br>      offset: 0,<br>      bufferIndex: 0)<br>    offset += MemoryLayout&lt;float3&gt;.stride<br>    vertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: offset)<br>    return vertexDescriptor<br>  }<br>}</pre><p>With these extensions in place, we can add a <strong>vertexDescriptor </strong>to our pipelineBuilder.</p><pre>import Metal<br><br>func buildPipeline(device: MTLDevice) -&gt; MTLRenderPipelineState {<br>    <br>    let pipeline : MTLRenderPipelineState<br>    <br>    let pipelineDescriptor = MTLRenderPipelineDescriptor()<br>    let library =  device.makeDefaultLibrary()!<br>    <br>   <br>    pipelineDescriptor.vertexFunction = library.makeFunction(name: &quot;vertexMain&quot;)<br>    pipelineDescriptor.fragmentFunction = library.makeFunction(name: &quot;fragmentMain&quot;)<br>    pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm<br>    //New Line<br>    pipelineDescriptor.vertexDescriptor = MTLVertexDescriptor.defaultLayout<br>    <br>    do{<br>        pipeline = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)<br>        return pipeline<br>    }catch{<br>        print(error.localizedDescription)<br>        fatalError()<br>    }<br>    <br>}</pre><p>That’s pretty much all the changes you need to make to draw 3D Models. Once more, you can find various free models on the internet to experiment with. To utlize all of these changes. Your draw call will look like this:</p><pre> func draw(in view: MTKView) {<br>....   <br>      renderEncoder.setRenderPipelineState(pipeline)<br><br>        //Draw our new Mesh from the Asset<br>        renderEncoder.setVertexBuffer(<br>          mesh.vertexBuffers[0].buffer,<br>          offset: 0,<br>          index: 0)<br>        <br>        for submesh in mesh.submeshes {<br>          renderEncoder.drawIndexedPrimitives(<br>                                  type: .triangle,<br>                                  indexCount: submesh.indexCount,<br>                                  indexType: submesh.indexType,<br>                                  indexBuffer: submesh.indexBuffer.buffer,<br>                                  indexBufferOffset: submesh.indexBuffer.offset<br>          )<br>        }<br>         renderEncoder.endEncoding()<br>        <br>        //Commit and present the state of the buffer<br>        commandBuffer.present(drawable)<br>        commandBuffer.commit()<br>        <br>    }</pre><p>To demonstarate this, I went to free3D.com and downloaded @<strong>printable_models’s </strong><a href="https://free3d.com/3d-model/cat-v1--522281.html"><strong>Cat Model</strong></a><strong>.</strong> If we want to see the texture and lighting applied, you’ll need to come back and read future parts.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1h1EJ94Xld_plgolA9YEfA.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=27c9cfd288c2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Swift x Metal for 3D Graphics Rendering Part 4: Drawing 3D Shapes And The Matrix]]></title>
            <link>https://carlosmbe.medium.com/swift-x-metal-for-3d-graphics-rendering-part-4-drawing-3d-shapes-and-the-matrix-598db38c86c0?source=rss-8e2b35a4a719------2</link>
            <guid isPermaLink="false">https://medium.com/p/598db38c86c0</guid>
            <category><![CDATA[rendering]]></category>
            <category><![CDATA[metal]]></category>
            <category><![CDATA[3d-graphics]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Carlos Mbendera]]></dc:creator>
            <pubDate>Mon, 27 Jan 2025 16:06:01 GMT</pubDate>
            <atom:updated>2025-01-27T16:06:01.282Z</atom:updated>
            <content:encoded><![CDATA[<p>Special Shoutout to @Caroline Begbie. As I was doing research for how to implement 3D Shapes in Metal with Swift Matrices. There was one name that showed up in most YouTube videos, Articles, Support Forums and Books. That being Caroline. So I’m without a doubt certain, that without her contribution to the Metal community. This article wouldn’t be made. Moreover, Caroline has a book and course on the topic that both seem super helpful for any learners. Moreover, the source code for the book is publicly available if you’re only interested in reading it.</p><ul><li><a href="https://www.kodeco.com/1258241-3d-graphics-with-metal">Kodeco’s (Ray Wenderlich) Metal Course</a> Taught by Caroline</li><li><a href="https://www.youtube.com/playlist?list=PL23Revp-82LJG3vcDPm8w7b5HTKjBOY0W">Old but FREE Kodeco Course on YouTube</a> Taught by Caroline</li><li><a href="https://github.com/kodecocodes/met-materials">Metal by Tutorials book Source Code</a> on GitHub</li><li>Metal By Tutorials Book (<a href="https://www.kodeco.com/books/metal-by-tutorials/v4.0">Kodeco</a>)</li></ul><h3>Show Me The Code</h3><p>Rendering in 3D isn’t that much different than rendering in 2D. Essentially, we need to modify our initial project in two ways to achieve it. Our set of vertices need to represent a 3D Obeject and we need a set of matrices telling our GPU how to make a 3D Object on a 2D Screen.</p><h4><strong>1. Vertices</strong></h4><p>We can get the vertices needed to draw a 3D object in a handful of ways. But for now, I will add a “Make Cube” function in our Mesh Builder.</p><pre> func makeCube() -&gt; Mesh {<br>        let s = Float(0.5)<br>        let vertices: [Vertex] = [<br>            Vertex(position: SIMD4&lt;Float&gt;(-s, -s,  s, 1), color: SIMD3&lt;Float&gt;(1, 0, 0)),<br>            Vertex(position: SIMD4&lt;Float&gt;( s, -s,  s, 1), color: SIMD3&lt;Float&gt;(0, 1, 0)),<br>            Vertex(position: SIMD4&lt;Float&gt;( s,  s,  s, 1), color: SIMD3&lt;Float&gt;(0, 0, 1)),<br>            Vertex(position: SIMD4&lt;Float&gt;(-s,  s,  s, 1), color: SIMD3&lt;Float&gt;(1, 1, 0)),<br>            Vertex(position: SIMD4&lt;Float&gt;(-s, -s, -s, 1), color: SIMD3&lt;Float&gt;(1, 0, 1)),<br>            Vertex(position: SIMD4&lt;Float&gt;( s, -s, -s, 1), color: SIMD3&lt;Float&gt;(0, 1, 1)),<br>            Vertex(position: SIMD4&lt;Float&gt;( s,  s, -s, 1), color: SIMD3&lt;Float&gt;(1, 1, 1)),<br>            Vertex(position: SIMD4&lt;Float&gt;(-s,  s, -s, 1), color: SIMD3&lt;Float&gt;(0, 0, 0))<br>        ]<br>        let indices: [UInt16] = [<br>            0,1,2,2,3,0,<br>            1,5,6,6,2,1,<br>            5,4,7,7,6,5,<br>            4,0,3,3,7,4,<br>            3,2,6,6,7,3,<br>            4,5,1,1,0,4<br>        ]<br>        let vb = device.makeBuffer(bytes: vertices, length: vertices.count * MemoryLayout&lt;Vertex&gt;.stride, options: [])!<br>        let ib = device.makeBuffer(bytes: indices, length: indices.count * MemoryLayout&lt;UInt16&gt;.stride, options: [])!<br>        return Mesh(vertexBuffer: vb, indexBuffer: ib, indexCount: indices.count)<br>    }</pre><p>To draw this, it will be quite literally identical to drawing our quad or triangle from the last part but with a different Mesh. In your Renderer’s Draw call, we’ll have:</p><pre>func draw(in view: MTKView) {<br>...<br>    renderEncoder.setVertexBuffer(cube.vertexBuffer, offset: 0, index: 0)<br>    renderEncoder.drawIndexedPrimitives(<br>        type: .triangle,<br>        indexCount: cube.indexCount,<br>        indexType: .uint16,<br>        indexBuffer: cube.indexBuffer,<br>        indexBufferOffset: 0)<br>...<br>     renderEncoder.endEncoding()<br>   <br>}</pre><p>However, if you run this, you’ll surely be disappointed and see your screen filled with the cube’s colors or nothing at all. But why? There’s a short and long answer to both of this question but essentially, its a lot of math. @<a href="https://www.youtube.com/@GetIntoGameDev">GetIntoGameDev</a>, @<a href="https://www.youtube.com/watch?v=3Mo8fUoxCgk&amp;list=PL23Revp-82LJG3vcDPm8w7b5HTKjBOY0W&amp;index=8">Caroline Begbie</a> and others have very detailed explanations of this. But for now, just now we need to create some matrices.</p><h4>2. The Matrix</h4><p>Firstly, let’s add Struct to our bridging header. We’ll call it “<strong><em>Uniforms.</em></strong>” This is how we will send our matrices to the GPU and create them on the CPU side.</p><pre>//In your .h file<br>#include &lt;simd/simd.h&gt;<br><br>struct Uniforms{<br>  matrix_float4x4 modelMatrix;<br>  matrix_float4x4 viewMatrix;<br>  matrix_float4x4 projectionMatrix;<br>};</pre><p>In the Renderer class. We want to add a property called Uniforms and then initialize it in the class’s init. Afterwards, we will create the projection matrix once we know the View’s size but before passing it to the GPU. At this point, we transition to the draw function and send the Uniforms.</p><pre>class Renderer : NSObject, MTKViewDelegate {<br>    //Other properties <br>    var uniforms : Uniforms<br>    ...<br>init(_ parent : ContentView) {<br>    ...<br>    uniforms = setUpUniforms()<br>    ...<br>    }<br><br> func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {<br>          //Create the Projection Matrix Using the CGSize from the View<br>          let aspect = Float(view.bounds.width) / Float(view.bounds.height)<br>          let projectionMatrix =<br>            createFloat4x4Projection(<br>              projectionFov: Float(70).degreesToRadians,<br>              near: 0.1,<br>              far: 100,<br>              aspect: aspect)<br>          uniforms.projectionMatrix = projectionMatrix<br>    }<br><br>  func draw(in view: MTKView) {<br>  ....<br>  // Send the Uniforms to the Shaders<br>  renderEncoder.setVertexBytes(&amp;uniforms,<br>                               length: MemoryLayout&lt;Uniforms&gt;.stride,<br>                               index: 1)<br>...<br>}<br><br>//Basic and Hardcoded Math Functions<br><br><br>func createFloat4x4Projection(projectionFov fov: Float, near: Float, far: Float, aspect: Float) -&gt; float4x4{<br>    let y = 1 / tan(fov * 0.5)<br>    let x = y / aspect<br>    let z = far / (far - near)<br>    let X = SIMD4&lt;Float&gt;( x,  0,  0,  0)<br>    let Y = SIMD4&lt;Float&gt;( 0,  y,  0,  0)<br>    let Z = SIMD4&lt;Float&gt;( 0,  0,  z, 1)<br>    let W = SIMD4&lt;Float&gt;( 0,  0,  z * -near,  0)<br>    return float4x4(columns: (X, Y, Z, W))<br>}<br><br>func setUpUniforms() -&gt; Uniforms{<br>  <br>    var uniforms = Uniforms()<br>    <br>    let translation = float4x4(<br>        SIMD4&lt;Float&gt;(1, 0, 0, 0),<br>        SIMD4&lt;Float&gt;(0, 1, 0, 0),<br>        SIMD4&lt;Float&gt;(0, 0, 1, 0),<br>        SIMD4&lt;Float&gt;(0, 0, 0, 1)<br>    )<br><br>   <br>    let angle = Float.pi / 6<br>    let rotation = float4x4(<br>        SIMD4&lt;Float&gt;(cos(angle), 0, sin(angle), 0),<br>        SIMD4&lt;Float&gt;(0, 1, 0, 0),<br>        SIMD4&lt;Float&gt;(-sin(angle), 0, cos(angle), 0),<br>        SIMD4&lt;Float&gt;(0, 0, 0, 1)<br>    )<br><br>    let modelMatrix = matrix_multiply(translation, rotation)<br>    let viewTranslation = float4x4(<br>        SIMD4&lt;Float&gt;(1, 0, 0, 0),<br>        SIMD4&lt;Float&gt;(0, 1, 0, 0),<br>        SIMD4&lt;Float&gt;(0, 0, 1, 0),<br>        SIMD4&lt;Float&gt;(0, 0, -2, 1)<br>    )<br><br>    let viewMatrix = viewTranslation.inverse<br>    <br>    uniforms.modelMatrix = modelMatrix<br>    uniforms.viewMatrix = viewMatrix<br><br>    return uniforms<br>}</pre><p>For convenience purposes, I will use a simple function called Create Uniforms which is very basic and has values hardcoded. However, in production, you’d likely create a set of functions to do common matrix graphics math for you. Both Caroline and GetIntoGameDev have publicly availably Swift functions showcasing this. I’d honestly opt for this if you’re making a personal project. If you want an deep understanding of what’s going on. I’d say write your own. You’ll most likely use<a href="http://Apple&#39;s SIMD Library"> Apple’s SIMD Library</a>.</p><p><strong>Here’s the updated Vertex Shader</strong>:</p><pre>VertexOutput vertex vertexMain(const device Vertex* vertices [[buffer(0)]],<br>                               uint vertexID [[vertex_id]],<br>                               constant Uniforms &amp;uniforms [[buffer(1)]])<br>{<br>    <br>    Vertex v = vertices[vertexID];<br>    VertexOutput data;<br>    float4 position = uniforms.projectionMatrix * uniforms.viewMatrix   * uniforms.modelMatrix * v.position;<br>    data.position = position;<br>    data.color = half3(v.color);<br>    return data;<br>}</pre><p>At this point. We should have a cube but it looks kinda weird.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mEwqqkXnYMQ3Y0DCEEvvIA.png" /></figure><p>To fix this we need to enable back culling (and possibly depth testing)</p><pre> func draw(in view: MTKView) {{<br>....   <br> let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!     <br>//After creating the render encoder, we set Cull Mode to back<br> renderEncoder.setCullMode(.back)<br>....<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9FM6wprwwfjbiaxekKEQsQ.png" /></figure><p>Congratulations! You now have a 3D Cube. If you wanted to have a 3D Model. We’d use a very similar process. But instead of using the 3D Cube we created. The vertices we load would come from a <strong><em>.obj </em></strong>file or some other source.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=598db38c86c0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Swift x Metal for 3D Graphics Rendering Part 3: Vertex and Index Buffers]]></title>
            <link>https://carlosmbe.medium.com/swift-x-metal-for-3d-graphics-rendering-part-3-vertex-and-index-buffers-56a1d155f316?source=rss-8e2b35a4a719------2</link>
            <guid isPermaLink="false">https://medium.com/p/56a1d155f316</guid>
            <category><![CDATA[metal]]></category>
            <category><![CDATA[3d-graphics]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[rendering]]></category>
            <dc:creator><![CDATA[Carlos Mbendera]]></dc:creator>
            <pubDate>Fri, 24 Jan 2025 16:06:01 GMT</pubDate>
            <atom:updated>2025-01-24T16:06:01.464Z</atom:updated>
            <content:encoded><![CDATA[<p>Special Shoutout to <a href="https://www.youtube.com/watch?v=eJq_5XLNeZM&amp;list=PLn3eTxaOtL2MEUsEZlhAyiSdoS08Xjwkq&amp;index=3">@GetIntoGameDev’s YouTube Video</a> for this. After this article, you should have all the building blocks and knowledege needed to start building your own 3D Rendering Apps. Specifically, we covered Shaders in Part 1. Building a Pipeline in Part 2.</p><p>Now we’re focused on connecting the CPU and GPU to draw things somewhat efficiently. So from after this part, theoretically, you’d mainly be doing math for projections, modifying shader code, optimization through things like Instanced Rendering, but all of that is on a project by project basis. If I write any more articles, they will be focused on things I want to try doing in Metal for fun. In particular, I would like to render the University of Kansas Mascot, Big Jay with Metal.</p><h3>Show Me The Code!</h3><p>We’re now looking into Buffers. This should allow us to no longer hard code values and send it directly to the GPU from the CPU.</p><p>Metal has a cool things called a Bridging Header that allows us to make this connection from the GPU and CPU fairly seamless.</p><h4><strong>Step 1: Create a new Header file in your project directory</strong></h4><p>Similar to how we create new Swift files. We want to create a Header File.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yyAL9K5s9rKCJ-_N-59I1g.png" /></figure><p>In the new Header. We will create a struct containing all the information a vertex should hold.</p><pre>#ifndef bridge_h<br>#define bridge_h<br>#include &lt;simd/simd.h&gt;<br><br>struct Vertex{<br>    // You&#39;ll probably expand this to suit your personal needs such as having Normals <br>    vector_float4 position;<br>    vector_float3 color;<br>};<br><br>#endif /* bridge_h */</pre><p>We’ll include the simd type and give our struct 2 properties for the position and color of a vertex.</p><p>Then go to Build Settings and assign the Objective-C Bridging Header to our new file. In my case, the path to the new header is <strong><em>“MacMetalExp/bridge.h”</em></strong> following the structure, <strong><em>“projectName/headerFileName”, </em></strong>so I enter that path in the Swift Compiler’s bridging header section. I’ve labelled the screenshot below to help you identify where in Xcode this section is.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j30GKf9EqRwSZlbdKSwm9Q.png" /></figure><p>The steps shown in the screenshot are as follows:</p><ol><li>Navigate to <strong>Project Settings</strong> <strong>→ Build Settings</strong></li><li>Filter down results to show only options with the word “header”</li><li>Scroll down the <strong>Swift Compiler — Genreal </strong>section</li><li>In the “Objective-C Bridging Header” field, enter the path to your Header file.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FGCYaxQ5HFSlNT8-D0DONw.png" /></figure><p>To confirm that everything is well set up. Try running and building your app. Also remember to clean the build folder if you’re having strange issues despite knowing your code is perfect.</p><h4><strong>Vertex Buffer</strong></h4><p>The Vertex Buffer is essentially a list of things we need to at draw a single point. In our current project, our vertex only has position and color assigned to it. However, with time we may have more attributes.</p><p>By Creating a Bridging Header, we make it possible for the CPU and GPU to communicate. So now, instead of hard coding the vertex information in the Shader. We’ll create a Vertex Buffer at creation and pass it over to the GPU.</p><p><strong>Let’s update our .shader file to reflect this.</strong></p><pre>#include &quot;bridge.h&quot;<br>#include &lt;metal_stdlib&gt;<br>using namespace metal;<br><br><br>struct VertexOutput {<br>    //Similar to how we qualify the Vertex and Fragment shader, we can qualify variables, hence the [[___]]<br>    //Here, we&#39;re telling Metal that this varibale is the actual position and some cool things happen under the hood<br>    float4 position [[position]];<br>    half3 color;<br>};<br><br>//Now we have a pointer to our Vertex Buffer as part of the Vertex Shader. Also, we qualify it as a buffer at Index 0<br>VertexOutput vertex vertexMain(const device Vertex* vertices [[buffer(0)]],<br>                               uint vertexID [[vertex_id]])<br>{<br>    //Instead of just loading the values from an array, we&#39;re now reading from the Vertex Struct we created earlier<br>    Vertex v = vertices[vertexID];<br>    VertexOutput data;<br>    data.position = v.position;<br>    data.color = half3(v.color);<br>    return data;<br>};<br><br>//Fragment Shader Receives the Output from the Vertex Shader as input<br>half4 fragment fragmentMain(VertexOutput frag [[stage_in]]){<br>    //Similarly we&#39;re qualifiying the input as a [[stage_in]]<br>    return half4(frag.color, 1.0);<br>    //Return Color as is, with an alpha of 1<br>};</pre><p><strong>Drawing A Triangle With A Vertex Buffer</strong></p><p>Continuing on the renderer code we wrote in Part 2. We can now make a Vertex Buffer with our triangles vertices. The process is as follows:</p><pre>//Make Vertex Buffer With Triangle Information<br> func makeTriangle() -&gt; MTLBuffer {<br>        <br>        let vertices: [Vertex] = [<br>            Vertex(position: [-0.75, -0.75, 0.0, 1.0], color: [1,1.0, 1.0]),<br>            Vertex(position: [0.75, -0.75, 0.0, 1.0], color: [1.0,1.0, 1.0]),<br>            Vertex(position: [0.0, 0.75, 0.0, 1.0], color: [1,0.3, 0.7])<br>            ]<br>        <br>        return device.makeBuffer(bytes: vertices,<br>                                 length: vertices.count * MemoryLayout&lt;Vertex&gt;.stride)!<br>        <br>    }</pre><p>Then when drawing, it would be as follows</p><pre>// In Renderer Class That Calls Draw Function<br>....<br>        renderEncoder.setRenderPipelineState(pipeline)<br>        <br>        //Add This line. We initizalie triangle to be the value of makeTriangle<br>        renderEncoder.setVertexBuffer(triangle, offset: 0, index: 0)<br><br>        renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)<br>        renderEncoder.endEncoding()</pre><h3><strong>Mesh Builder Class and Index Buffer</strong></h3><p>However, we can also make a mesh builder class. So if we want to make and render a complex shapes we can easily expand. Likewise it’d be nice to put our make Triangle function there. And while we’re at it. Let’s introduce Indexed drawing by drawing a quad behind our triangle.</p><h4><strong>Mesh Builder</strong></h4><pre><br>import Metal<br><br>struct Mesh{<br>    //Makes it easier to bundle together data<br>    let vertexBuffer: MTLBuffer<br>    let indexBuffer: MTLBuffer<br>    let indexCount: Int<br>}<br><br>class MeshBuilder {<br>    <br>    let device: MTLDevice<br>    <br>    init(device: MTLDevice) {<br>        self.device = device<br>    }<br>    <br>    func makeTriangle() -&gt; MTLBuffer {<br>        <br>        let vertices: [Vertex] = [<br>            Vertex(position: [-0.75, -0.75, 0.0, 1.0], color: [1,1.0, 1.0]),<br>            Vertex(position: [0.75, -0.75, 0.0, 1.0], color: [1.0,1.0, 1.0]),<br>            Vertex(position: [0.0, 0.75, 0.0, 1.0], color: [1,0.3, 0.7])<br>            ]<br>        <br>        return device.makeBuffer(bytes: vertices,<br>                                 length: vertices.count * MemoryLayout&lt;Vertex&gt;.stride)!<br>        <br>    }<br>    <br>    <br>    func makeQuad() -&gt; Mesh {<br>        <br>        //Points in our quad<br>        let vertices: [Vertex] = [<br>            Vertex(position: [-0.75, -0.75, 0.0, 1.0], color: [1,1, 1]),<br>            Vertex(position: [0.75, -0.75, 0.0, 1.0], color: [1,1, 1]),<br>            Vertex(position: [0.75, 0.75, 0.0, 1.0], color: [1,1, 1]),<br>            Vertex(position: [-0.75, 0.75, 0.0, 1.0], color: [1,1,1])<br>            ]<br>        <br>        //Order in which we want to draw lines between points<br>        let indices: [UInt16] = [ 0, 1, 2, 2, 3, 0 ]<br>            <br>        <br>        //Making and combining the buffers for both the Vertices and indices<br>        let vertexBuffer = device.makeBuffer(bytes: vertices,<br>                                 length: vertices.count * MemoryLayout&lt;Vertex&gt;.stride)!<br>        <br>        let indexBuffer = device.makeBuffer(bytes: indices,<br>                                            length: indices.count * MemoryLayout&lt;UInt16&gt;.stride)!<br>        <br>        <br>        return Mesh(vertexBuffer: vertexBuffer,<br>                    indexBuffer: indexBuffer,<br>                    indexCount: indices.count)<br>    }<br>    <br>    <br>}</pre><h4><strong>Final Render Code With Quad/Mesh Drawing</strong></h4><pre><br>import MetalKit<br><br>///Put metal code here<br>class Renderer : NSObject, MTKViewDelegate {<br>    var parent : ContentView<br>    var device : MTLDevice!<br>    var commandQueue : MTLCommandQueue!<br>    <br>    let triangle: MTLBuffer<br>    let quad : Mesh<br>    <br>    var pipeline: MTLRenderPipelineState<br>    <br>    init(_ parent : ContentView) {<br>        self.parent = parent<br>       <br>        if let device = MTLCreateSystemDefaultDevice() {<br>            self.device = device<br>        }<br>        <br>        self.commandQueue = device.makeCommandQueue()<br>        <br>        //Adding our Pipeline Builder<br>        pipeline = buildPipeline(device: device)<br>        <br>        let meshBuilder = MeshBuilder(device: device)<br>        triangle = meshBuilder.makeTriangle()<br>        quad = meshBuilder.makeQuad()<br>        <br>        super.init()<br>    }<br>    <br>    <br>    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {<br>    }<br>    <br>    ///Drawing Pass<br>    func draw(in view: MTKView) {<br>        guard let drawable = view.currentDrawable else {    return  }<br>        <br>        let commandBuffer = commandQueue.makeCommandBuffer()!<br>        let renderPassDescriptor = view.currentRenderPassDescriptor!<br>        //Render Pass - Clear - Set Clear Colour<br>        renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.7, green: 0.3, blue: 0.6, alpha: 1.0)<br>        renderPassDescriptor.colorAttachments[0].loadAction = .clear<br>        renderPassDescriptor.colorAttachments[0].storeAction = .store<br>        <br>        let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!<br>        <br>        //Updates so draw calls are made<br>        renderEncoder.setRenderPipelineState(pipeline)<br>        <br>        renderEncoder.setVertexBuffer(quad.vertexBuffer, offset: 0, index: 0)<br>        renderEncoder.drawIndexedPrimitives(type: .triangle, indexCount: quad.indexCount, indexType: .uint16, indexBuffer: quad.indexBuffer, indexBufferOffset: 0)<br>        <br>        renderEncoder.setVertexBuffer(triangle, offset: 0, index: 0)<br>        renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)<br>        <br>        <br>        <br>        renderEncoder.endEncoding()<br>        <br>        //Commit and present the state of the buffer<br>        commandBuffer.present(drawable)<br>        commandBuffer.commit()<br>        <br>    }<br>    <br>}</pre><p>Congratulations, you have a triangle on top of a quad. If it’s crashing right now, try cleaning your build folder.</p><p>Yes, if you copied and pasted my complete functions, you may have noticed I changed the background color again after part 2. Why? The answer is simple, Why not?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1nie1IWvdI4myeiuD7s89g.png" /></figure><p>At this point, you can build a lot of things. Assuming you have a background in Computer Graphics. Now that you know how to write shaders and send data from the CPU side. All that’s left is to let your imagination go wild and do a lot of math.</p><p>In the next installment, I’ll attempt to render a 3D Mesh and possibly introduce a basic lighting model to our shader.</p><p>So now, instead of hard coding the vertex information in the Shader. We’ll create a Vertex Buffer at creation and pass it over to the GPU.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=56a1d155f316" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Swift x Metal for 3D Graphics Rendering Part 2: Metal Shaders and Drawing A Triangle]]></title>
            <link>https://carlosmbe.medium.com/swift-x-metal-for-3d-graphics-rendering-part-2-metal-shaders-and-drawing-a-triangle-bf6803c108d4?source=rss-8e2b35a4a719------2</link>
            <guid isPermaLink="false">https://medium.com/p/bf6803c108d4</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[metal]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[3d-graphics]]></category>
            <dc:creator><![CDATA[Carlos Mbendera]]></dc:creator>
            <pubDate>Mon, 20 Jan 2025 16:06:01 GMT</pubDate>
            <atom:updated>2025-01-20T16:06:01.127Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article, I’ll walk you through drawing a triangle in Metal with Swift and SwiftUI. If something seems confusing, please read the first part of this series <a href="https://medium.com/@carlosmbe/swift-x-metal-for-3d-graphics-rendering-part-1-setting-up-in-swiftui-d2e90d6e5ec3">here</a>. This article will mainly show you how to create the Vertex and Fragment Shaders, make a draw call and see the output. Also, and quite importantly, if you follow all of these steps and your app is crashing. Try Cleaning your build folder.</p><p>Special shout out to @<a href="https://www.youtube.com/@GetIntoGameDev">GetIntoGameDev</a>’s YouTube channel and work.</p><h3><strong>Show Me The Code</strong></h3><p>After setting up your Metal Kit Views, you’ll want to create shaders and make draw calls to actually render something.</p><h4><strong>Creating Shaders</strong></h4><p>In your Xcode Project, you’ll want to create a new <strong><em>“.metal”</em></strong> file. Feel free to name it whatever you want. Unlike OpenGL, we can put both the Vertex and Fragment Shader in the same file. We just need to create their functions and give them the qualifier “<strong>vertex</strong>” or “<strong>fragment</strong>” accordingly. One more thing, is that we also need to create a <strong><em>struct</em></strong> that contains the Vertex Shader’s data.</p><p>Putting all of this together, our <strong><em>.metal </em></strong>file should look like this.</p><pre>#include &lt;metal_stdlib&gt;<br>using namespace metal;<br><br>struct VertexOutput {<br>    //Similar to how we qualify the Vertex and Fragment shader, we can qualify variables, hence the [[___]]<br>    //Here, we&#39;re telling Metal that this varibale is the actual position and some cool things happen under the hood<br>    float4 position [[position]];<br>    half3 color;<br>};<br><br>//Vertex ID is the index of the current Vertex<br>VertexOutput vertex vertexMain(uint vertexID [[vertex_id]]){<br>    VertexOutput data;<br>    data.position = positions[vertexID];<br>    data.color = colors[vertexID];<br>    return data;<br>};<br><br>//Fragment Shader Receives the Output from the Vertex Shader as input<br>half4 fragment fragmentMain(VertexOutput frag [[stage_in]]){<br>    //Similarly we&#39;re qualifiying the input as a [[stage_in]]<br>    return half4(frag.color, 1.0);<br>    //Return Color as is, with an alpha of 1<br>};</pre><h4>Drawing A Triangle</h4><p>To draw a Triangle with our new Shaders, we need to modify our starter code in 3ways. Firstly, we need to add the vertices and colors of the triangle we’re drawing. Secondly, we need to combine all the aspects of our rendering pipeline together. Lastly, we need to make a draw call to render our triangle.</p><p><strong>Step 1: Adding Our Triangle’s Information</strong></p><p>For educational purposes, we’ll just hard code these in the <strong><em>.metal </em></strong>file. However, in practice, we’d load this from elsewhere. Possibly a mesh.</p><pre><br>#include &lt;metal_stdlib&gt;<br>using namespace metal;<br><br>//Hard coded data for learning purposes, don&#39;t this in real projects<br>constant float4 positions[] = {<br>    //X -1== Left, 1 == Right, 0 == Centre<br>    float4(-0.75, -0.75, 0.0, 1.0), //Bottom Left<br>    float4(0.75, -0.75, 0.0, 1.0), //Bottom Right<br>    float4(0.0, 0.75, 0.0, 1.0), //Centre Top<br>};<br><br>constant half3 colors[] = {<br>    //RGB<br>    half3(1,0.0, 1.0), //Bottom Left<br>    half3(0.0, 0.0, 1.0), //Bottom Right<br>    half3(1.0,1.0, 1.0) //Centre Top<br>};<br><br>//Shaders Come Here ....</pre><p><strong>Step 2: Build Our Pipeline</strong></p><p>At this point, we’ll create a function that builds a pipeline using the shaders and Metal Device. In practice, this function would be more generic but for now we’ve hardcoded the names of the shader.</p><pre>import Metal<br><br>func buildPipeline(device: MTLDevice) -&gt; MTLRenderPipelineState {<br>    <br>    let pipeline : MTLRenderPipelineState<br>    <br>    let pipelineDescriptor = MTLRenderPipelineDescriptor()<br>    let library =  device.makeDefaultLibrary()!<br>    <br>    //We&#39;re hardcoding these, but realistically in a more complex app, we&#39;d accept these as and map it accordingly<br>    pipelineDescriptor.vertexFunction = library.makeFunction(name: &quot;vertexMain&quot;)<br>    pipelineDescriptor.fragmentFunction = library.makeFunction(name: &quot;fragmentMain&quot;)<br>    pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm<br>    <br>    do{<br>        pipeline = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)<br>        return pipeline<br>    }catch{<br>        //Since we&#39;re hardcoding things right now, we know that is should work, otherwise in practise, our code would handle this better<br>        //If it does crash, try cleaning your build folder<br>        fatalError()<br>    }<br>    <br>}</pre><p><strong>Step 3: Draw Calls</strong></p><p>This Renderer class is mostly the same as the one from the previous part. The main difference is that we now have a pipeline variable of type <strong><em>MTLRenderPipelineState</em></strong> we initialize it with the function from step 2 and use it in the draw function with renderEncoder.</p><pre>import MetalKit<br><br>///Put metal code here<br>class Renderer : NSObject, MTKViewDelegate {<br>    var parent : ContentView<br>    var device : MTLDevice!<br>    var commandQueue : MTLCommandQueue!<br>    <br>    var pipeline: MTLRenderPipelineState<br>    <br>    init(_ parent : ContentView) {<br>        self.parent = parent<br>       <br>        if let device = MTLCreateSystemDefaultDevice() {<br>            self.device = device<br>        }<br>        <br>        self.commandQueue = device.makeCommandQueue()<br>        <br>        //Adding our Pipeline Builder<br>        pipeline = buildPipeline(device: device)<br>        <br>        super.init()<br>    }<br>    <br>    <br>    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {<br>    }<br>    <br>    ///Drawing Pass<br>    func draw(in view: MTKView) {<br>        guard let drawable = view.currentDrawable else {    return  }<br>        <br>        let commandBuffer = commandQueue.makeCommandBuffer()!<br>        let renderPassDescriptor = view.currentRenderPassDescriptor!<br>        //Render Pass - Clear - Set Clear Colour<br>        renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.8, green: 0.3, blue: 0.6, alpha: 1.0)<br>        renderPassDescriptor.colorAttachments[0].loadAction = .clear<br>        renderPassDescriptor.colorAttachments[0].storeAction = .store<br>        <br>        let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!<br>        <br>        //Updates so draw calls are made<br>        renderEncoder.setRenderPipelineState(pipeline)<br>        renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)<br>        <br>        renderEncoder.endEncoding()<br>        <br>        //Commit and present the state of the buffer<br>        commandBuffer.present(drawable)<br>        commandBuffer.commit()<br>        <br>    }<br>    <br>}</pre><h3><strong>Closing Words</strong></h3><p>Congratulations, you have a triangle. If it’s crashing right now, <strong>try cleaning your build folder.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bzEy0H7xysrc2Nuw8kyfhQ.png" /></figure><p>Also, if you copied and pasted my complete functions, you may have noticed I changed the background color from the one in Part 1. Why? The answer is simple, Why not?</p><p>Once more, special shout out to @<a href="https://www.youtube.com/@GetIntoGameDev">GetIntoGameDev</a>’s YouTube channel. You can find their GitHub repo documenting a similar set up here: <a href="https://github.com/amengede/getIntoMetalDev">https://github.com/amengede/getIntoMetalDev</a></p><p>Alternatively, you can follow <a href="https://github.com/carlosmbe/LearningMetal">this GitHub Repo </a>that I will push my code to as I develop it and also have links to all the future articles I write in this series.</p><p>If I’ve made any errors, or you have a really cool way of implementing something, feel free to leave a comment.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bf6803c108d4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Swift x Metal for 3D Graphics Rendering Part 1: Setting Up in SwiftUI]]></title>
            <link>https://carlosmbe.medium.com/swift-x-metal-for-3d-graphics-rendering-part-1-setting-up-in-swiftui-d2e90d6e5ec3?source=rss-8e2b35a4a719------2</link>
            <guid isPermaLink="false">https://medium.com/p/d2e90d6e5ec3</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[metal]]></category>
            <category><![CDATA[3d-graphics]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Carlos Mbendera]]></dc:creator>
            <pubDate>Mon, 13 Jan 2025 16:06:03 GMT</pubDate>
            <atom:updated>2025-01-13T16:06:03.554Z</atom:updated>
            <content:encoded><![CDATA[<p>When I started developing apps for the Apple Ecosystem in 2022, SwiftUI was fairly capable. Hence, as a result, UIKit was something I only interacted with when Apple had a really powerful library that was created in the UIKit or Objective-C days but was not popular enough to have a SwiftUI implementation.</p><p>Unfortunately, from my research, I believe Metal and Metal Kit Views are such a framework. Thus, setting up a View to see your rendering pipeline’s work has a few more steps then I’d like. But don’t worry, I got you, however, first I would like to give a special shout out to @<a href="https://www.youtube.com/@GetIntoGameDev">GetIntoGameDev</a>’s YouTube channel.</p><p>I’m not delving too deeply into the theory of 3D Graphics Rendering, I’m a not professor, just an enthusiast who likes writing Swift. For those interested in learning Metal, I found these resources.</p><ul><li>@<a href="https://www.youtube.com/@GetIntoGameDev">GetIntoGameDev</a>’s YouTube channel</li><li><a href="https://www.kodeco.com/1258241-3d-graphics-with-metal">(Paid) Kodeco Course</a></li><li><a href="https://www.youtube.com/playlist?list=PL23Revp-82LJG3vcDPm8w7b5HTKjBOY0W">Old but FREE Kodeco Course on YouTube</a></li></ul><h3>Show Me The Code</h3><p>I’m assuming that you’ve already created a new project on Xcode. First things first, we need to modify the Content View to show our rendered content. If you come from an OpenGL and C++ background and not iOS and Swift, you can think of this portion as somewhat similar to using GLFW to create a window for our OpenGL rendering. Here we’re using SwiftUI (really UIKit) to do something similar for Metal.</p><h4>Content View</h4><pre>import SwiftUI<br>import MetalKit<br><br>struct ContentView: UIViewRepresentable {<br>    ///Create UI Kit View to Set Up Metal Kit View<br>    <br>    func makeCoordinator() -&gt; Renderer {<br>        // We are going to create this class elsewhere, so don&#39;t be shocked if you&#39;re getting a few errors here<br>        Renderer(self)<br>    }<br>    <br>    func makeUIView(context: UIViewRepresentableContext&lt;ContentView&gt;) -&gt; MTKView {<br>        // We&#39;re using the Metal Kit library to create a Metal Kit view. You can customize this section with your own parameters<br>        let mtkView = MTKView()<br>        mtkView.delegate = context.coordinator<br>        mtkView.preferredFramesPerSecond = 60<br>        mtkView.enableSetNeedsDisplay = true<br>        <br>        <br>        if let metalDevice = MTLCreateSystemDefaultDevice() {<br>            mtkView.device = metalDevice<br>        }<br>        <br>        mtkView.framebufferOnly = false<br>        mtkView.drawableSize = mtkView.frame.size<br>        return mtkView<br>    }<br>        <br>    func updateUIView(_ uiView: MTKView, context: UIViewRepresentableContext&lt;ContentView&gt;) {<br>        // Place Holder function for now. We&#39;ll implement this later but for now, we need it to meet the UIViewRepresentable Prototype requirements <br>    }<br>    <br>}</pre><h4>Renderer Class</h4><p>Next we create a class to handle our drawing. For this section, all we’re really doing is setting a background color. In the future, we will make our drawing calls from here.</p><pre>import MetalKit<br><br>///Put metal code here<br>class Renderer : NSObject, MTKViewDelegate {<br>    var parent : ContentView<br>    var device : MTLDevice!<br>    var commandQueue : MTLCommandQueue!<br>    <br>    init(_ parent : ContentView) {<br>        self.parent = parent<br>       <br>        if let device = MTLCreateSystemDefaultDevice() {<br>            self.device = device<br>        }<br>        <br>        self.commandQueue = device.makeCommandQueue()<br>        <br>        super.init()<br>    }<br>    <br>    <br>    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {<br>    }<br>    <br>    ///Drawing Pass<br>    func draw(in view: MTKView) {<br>        guard let drawable = view.currentDrawable else {    return  }<br>        <br>        let commandBuffer = commandQueue.makeCommandBuffer()!<br>        let renderPassDescriptor = view.currentRenderPassDescriptor!<br>        //Render Pass - Clear - Set Clear Colour<br>        renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.5, blue: 0.5, alpha: 1.0)<br>        renderPassDescriptor.colorAttachments[0].loadAction = .clear<br>        renderPassDescriptor.colorAttachments[0].storeAction = .store<br>        <br>        let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!<br>        renderEncoder.endEncoding()<br>        <br>        //Commit and present the state of the buffer<br>        commandBuffer.present(drawable)<br>        commandBuffer.commit()<br>        <br>    }<br>    <br>}</pre><h3>Closing Words</h3><p>In the next article, I’ll do the Graphics equivalent of Hello World, draw a triangle and some other cool things as I learn the Swifty way doing them. This portion was focused on setting up your project. Once more, special shout out to @<a href="https://www.youtube.com/@GetIntoGameDev">GetIntoGameDev</a>’s YouTube channel. You can find their GitHub repo documenting a similar set up here: <a href="https://github.com/amengede/getIntoMetalDev">https://github.com/amengede/getIntoMetalDev</a></p><p>Alternatively, you can follow <a href="https://github.com/carlosmbe/LearningMetal">this GitHub Repo </a>that I will push my code to as I develop it and also have links to all the future articles I write in this series.</p><p>If I’ve made any errors, or you have a really cool way of implementing something, feel free to leave a comment.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d2e90d6e5ec3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Running Modern OpenGL on macOS: A Virtualized Ubuntu Approach]]></title>
            <link>https://medium.com/codex/running-modern-opengl-on-macos-a-virtualized-ubuntu-approach-4730c99dc611?source=rss-8e2b35a4a719------2</link>
            <guid isPermaLink="false">https://medium.com/p/4730c99dc611</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[macos]]></category>
            <category><![CDATA[opengl]]></category>
            <category><![CDATA[visualization]]></category>
            <category><![CDATA[3d-graphics]]></category>
            <dc:creator><![CDATA[Carlos Mbendera]]></dc:creator>
            <pubDate>Sat, 04 Jan 2025 13:26:31 GMT</pubDate>
            <atom:updated>2025-01-04T13:26:31.150Z</atom:updated>
            <content:encoded><![CDATA[<p>Recently, I have been learning the nitty gritty of Computer Graphics with a focus on 2D and 3D rendering. The course I’m taking focuses on 3D Rendering Pipelines built with OpenGL 4.3. However, as a proud MacBook user, I’m at a loss since Apple discontinued support for OpenGL on MacOS in 2011 with OpenGL 4.1. Fortunately, I’m persistent if not resourceful and I was able to set up a Virtual Machine with access to my Mac’s files that had OpenGL 4.5 running. In this article, I’ll walk you through what I did, learned and possible alternatives.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*cEc0sno_sbXW8bOH" /><figcaption>Photo by <a href="https://unsplash.com/@cgower?utm_source=medium&amp;utm_medium=referral">Christopher Gower</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h4>Options: Virtualization? Porting?</h4><p>Before, diving into my exact set up, I’d like to share some context on my choices, in case you’d like to further research.</p><p><strong>Porting over newer OpenGL to MacOS</strong></p><p>Initially, I hoped that despite Apple’s discontinued support for OpenGL, there would be a dedicated group of developers unofficially porting it over. I would like to acknowledge the <a href="https://github.com/openglonmetal/MGL">OpenGL 4.6 on Metal</a> project on GitHub for doing exactly that. Unfortunately, this was not a good fit for my use case but could be perfect for yours.</p><p><strong>Virtualization and Emulation</strong></p><p>As a result, I concluded that it would be better for me to have an easily accessible Linux distro on my computer instead. Which involved its own configuration quirks, especially on Apple Silicon Macs.</p><p>After doing some research, I found that popular methods of running Linux VMs included:</p><ul><li>Parallels (Paid)</li><li><a href="https://blogs.vmware.com/teamfusion/2024/05/fusion-pro-now-available-free-for-personal-use.html">VM Fusion </a>(Free if for individual use)</li><li><a href="https://mac.getutm.app">UTM</a> (Free)</li></ul><p>Personally, I ended up using VM Fusion with <a href="https://ubuntu.com/download/server/arm">ARM Ubuntu Server</a> with Ubuntu Desktop GUI installed onto said server. Why? Because, as far as I know, there’s no ARM version of Ubuntu Desktop ISO but there is one for Ubuntu Server. Thus, having Apple Silicon Macs have a few extra steps to follow. Please feel free to leave a comment, if I have made any mistakes or if there’s an easier way to get this set up.</p><p>For setting up Ubuntu Server after installing it with one of the options above. You can follow <a href="https://techblog.shippio.io/how-to-run-an-ubuntu-22-04-vm-on-m1-m2-apple-silicon-9554adf4fda1">this tutorial</a>. It covers the UTM process in great detail, however, VM Fusion users should also follow step 4 onwards.</p><p>Once you get the Virtual Machine running, shared folders set up and can confidently build your OpenGL project on the Linux side. I would strongly recommend making a snapshot of your VM here.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OrURG09tBuxs0TmNmETFEw.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4730c99dc611" width="1" height="1" alt=""><hr><p><a href="https://medium.com/codex/running-modern-opengl-on-macos-a-virtualized-ubuntu-approach-4730c99dc611">Running Modern OpenGL on macOS: A Virtualized Ubuntu Approach</a> was originally published in <a href="https://medium.com/codex">CodeX</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building A Local OLLAMA App for your Mac with Swift]]></title>
            <link>https://medium.com/codex/building-a-local-llama-3-app-for-your-mac-with-swift-e96f3a77c0bb?source=rss-8e2b35a4a719------2</link>
            <guid isPermaLink="false">https://medium.com/p/e96f3a77c0bb</guid>
            <category><![CDATA[llama-3]]></category>
            <category><![CDATA[mac-development]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[large-language-models]]></category>
            <dc:creator><![CDATA[Carlos Mbendera]]></dc:creator>
            <pubDate>Sun, 28 Apr 2024 07:54:09 GMT</pubDate>
            <atom:updated>2025-01-31T14:35:24.355Z</atom:updated>
            <content:encoded><![CDATA[<p><em>EDIT: With the release of Deepseek R1 and multiple new open source models on Ollama. I’d like to acknowledge that this app does in fact support all of them and that since writing this article. I’ve added new features to make that easier.</em></p><p>Recently, Meta released LLAMA 3 and allowed the masses to use it (made it open source).</p><p>Wanting to test how fast the new MacBook Pros with the fancy M3 Pro chip can handle on device Language Models, I decided to download the model and make a Mac App to chat with the model from my Menu Bar at all times. In this article, I will you teach you, <strong>YES YOU</strong>, how to deploy your own language model <strong>locally</strong> onto your Mac.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/861/1*oujqhHJsyQicZAQRKVgxMw.png" /><figcaption>Generated by DALL-E on April 26 with Chat GPT 4</figcaption></figure><h3>Setting Up Ollama / Deploying a Local Model</h3><p>This part is relatively simple. Thank you Ollama developers!</p><p>First step, <a href="https://ollama.com">ollama.com</a> and follow their download and set up instructions.</p><p>Namely, you will download the Ollama App, after opening it, you will go through a set up process that installs Ollama to your Mac.</p><p>After installing Ollama, we can download and run our model. For this article, we will use <strong>LLAMA3:8b</strong> because that’s what my M3 Pro 32GB Memory Mac Book Pro runs the best.</p><p>To do that, we’ll <strong>open the Terminal </strong>and<strong> </strong>type in</p><pre> ollama run llama3</pre><p>This will download the model and start a Text Interface where you can interact with the model via the terminal.</p><p>Since I have run this command before, using this command simply starts the model and does not cause it download once more.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W_tdORJdReGwERcQxW2aaw.png" /></figure><p>You can exit this interface with the command:</p><pre>Control + d</pre><h3>Show Me The Code / Making A Mac Menu Bar App To Chat With The Local Model</h3><p>By the end, our app will look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*alJJuSWPWK2QKvV_TyKXkA.gif" /><figcaption>Screen Recording By Yours Truly. You can find the full video on my GitHub.</figcaption></figure><p>If you want to see the full project, it’s on my GitHub, here <a href="https://github.com/carlosmbe/MyMacLLAMA">https://github.com/carlosmbe/MyMacLLAMA</a></p><p>I will be updating the project there with new features every once in a while. So this article will focus on the basics and essentials.</p><h3><strong><em>The Data Model and Fun Logic:</em></strong></h3><p><strong><em>Things to Note:</em></strong><br>1. The address, <a href="http://127.0.0.1:11434/">http://127.0.0.1:11434/</a> is the <strong>local host at port 11434</strong>. Which the default port that Ollama runs on. What we’re doing here is doing an API call to our own device as the server.</p><p>2. Furthermore, since this is an API Call, we need explicitly request permission for incoming and outgoing network calls in the App capabilities.</p><pre>import Foundation<br><br>// Struct to decode the JSON response<br>struct Response: Codable {<br>    let model: String<br>    let response: String<br>}<br><br>// Class for managing application data and network communication<br>class DataInterface: ObservableObject, Observable {<br>    <br>    // Store the current prompt as a modifiable string<br>    @Published var prompt: String = &quot;&quot;<br>    // Store the response to the prompt as a modifiable string<br>    @Published var response: String = &quot;&quot;<br>    // Track whether a network request is currently being sent<br>    @Published var isSending: Bool = false<br><br>    // Function to handle sending the prompt to a server<br>    func sendPrompt() {<br>        print(&quot;Started Send Prompt&quot;)  // Log the start of sending a prompt<br>        // Prevent sending if the prompt is empty or a request is already in progress<br>        guard !prompt.isEmpty, !isSending else { return }<br>        isSending = true  // Mark that a sending process has started<br>        <br>        // Define the server endpoint<br>        let urlString = &quot;http://127.0.0.1:11434/api/generate&quot;<br>        // Safely unwrap the URL constructed from the urlString<br>        guard let url = URL(string: urlString) else { return }<br>        <br>        // Prepare the network request with the URL<br>        var request = URLRequest(url: url)<br>        request.httpMethod = &quot;POST&quot;  // Set the HTTP method to POST<br>        request.addValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)  // Set the content type to JSON<br>        let body: [String: Any] = [<br>            &quot;model&quot;: &quot;llama3&quot;,  // Specify the model to be used<br>            &quot;prompt&quot;: prompt,  // Pass the prompt<br>            &quot;options&quot;: [<br>                &quot;num_ctx&quot;: 4096  // Specify context options<br>            ]<br>        ]<br>        // Encode the request body as JSON<br>        request.httpBody = try? JSONSerialization.data(withJSONObject: body)<br>        <br>        // Start the data task with the request<br>        URLSession.shared.dataTask(with: request) { data, response, error in<br>            defer { DispatchQueue.main.async { self.isSending = false } }  // Ensure isSending is reset after operation<br>            if let error = error {<br>                DispatchQueue.main.async { self.response = &quot;Error: \(error.localizedDescription)&quot; }  // Handle errors by updating the response<br>                return<br>            }<br>            <br>            // Ensure data was received<br>            guard let data = data else {<br>                DispatchQueue.main.async { self.response = &quot;No data received&quot; }  // Handle the absence of data<br>                return<br>            }<br>            <br>            let decoder = JSONDecoder()  // Initialize JSON decoder<br>            let lines = data.split(separator: 10)  // Split the data into lines<br>            var responses = [String]()  // Array to hold the decoded responses<br>            <br>            // Iterate over each line of data<br>            for line in lines {<br>                if let jsonLine = try? decoder.decode(Response.self, from: Data(line)) {<br>                    responses.append(jsonLine.response)  // Decode each line and append the response<br>                }<br>            }<br>            <br>            print(responses)  // Log all responses<br>            <br>            DispatchQueue.main.async {<br>                self.response = responses.joined(separator: &quot;&quot;)  // Combine all responses into one string<br>                print(self.response)  // Print the full response<br>            }<br>        }.resume()  // Resume the task if it was suspended<br>    }<br>}</pre><h3>The User Interface</h3><p><strong><em>The Menu Bar:</em></strong></p><p>We will go to the App file to make the app operate solely in the Menu Bar. For more information on the Menu Bar Section. Check out this article, its what I used as a foundation. <br><a href="https://sarunw.com/posts/swiftui-menu-bar-app/">https://sarunw.com/posts/swiftui-menu-bar-app/</a></p><pre><br>import SwiftUI<br><br>@main<br>struct MyMacLLAMAApp: App {<br>    var body: some Scene {<br>        <br>        //Create instance of Data Interface for the app<br>        @StateObject var appModel = DataInterface()<br>        <br>        //Create a Menu Bar for our app and have it have the brain icon<br>        MenuBarExtra(&quot;My Mac LLAMA Bar&quot;, systemImage: &quot;brain&quot;){<br>            //When Clicked The Menu Bar Will Show the content View<br>            ContentView()<br>                .environment(appModel)// Pass the appModel environment object to ContentView<br><br>        }<br>        //This modifier lets us show a Window in the Menu Bar<br>        .menuBarExtraStyle(.window)<br>        <br>        <br>    }<br>}</pre><p><strong><em>Content View:</em></strong></p><p>To make the user interface, I will make a simple Content View with a TextField, a Submit Button and a Text View for the Response.</p><pre>struct ContentView: View {<br>    <br>    // I will use the EnvironmentObject property wrapper to share data between this view and others <br>     @EnvironmentObject var appModel: DataInterface<br>    <br>    var body: some View {<br>        VStack {<br>            <br>            // TextField for the user input .<br>            TextField(&quot;Prompt&quot;, text: $appModel.prompt)<br>                .textFieldStyle(.roundedBorder) <br>                .onSubmit(appModel.sendPrompt) // Send the prompt to Ollama and get a response<br>            <br>            // Divider draws a line separating elements<br>            Divider()<br>            <br>            // Use an if statement to conditionally display a view depending on if appModel.isSending.<br>            if appModel.isSending{<br>                ProgressView() // Display a progress bar while waiting for a response.<br>                    .padding() <br>            }else{<br>                Text(appModel.response) // Display the response text from appModel if not currently sending.<br>            }<br>            <br>           <br>            HStack{<br>                <br>                // Button to send the current prompt. It triggers the sendPrompt function when clicked.<br>                Button(&quot;Send&quot;){<br>                    appModel.sendPrompt()<br>                }<br>                .keyboardShortcut(.return) // Assign the return key as a shortcut to activate this button. Cause Mac.<br>                <br>                // Button to clear the current prompt and response.<br>                Button(&quot;Clear&quot;){<br>                    appModel.prompt = &quot;&quot; // Clear the prompt string.<br>                    appModel.response = &quot;&quot; // Clear the response string.<br>                }<br>                .keyboardShortcut(&quot;c&quot;) // Assign the &#39;c&#39; key as a shortcut to activate this button. So Command + C<br>                <br>            }<br>        }<br>        .padding()<br>    }<br>}</pre><h3>Links:</h3><p>Final Project: <a href="https://github.com/carlosmbe/MyMacLLAMA">https://github.com/carlosmbe/MyMacLLAMA</a></p><p>Ollama: <a href="https://ollama.com">https://ollama.com</a></p><p>Tutorial on Menu Bar Mac Apps: <a href="https://sarunw.com/posts/swiftui-menu-bar-app/">https://sarunw.com/posts/swiftui-menu-bar-app/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e96f3a77c0bb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/codex/building-a-local-llama-3-app-for-your-mac-with-swift-e96f3a77c0bb">Building A Local OLLAMA App for your Mac with Swift</a> was originally published in <a href="https://medium.com/codex">CodeX</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>