Shipping My New VisionOS App: novision

Thabhelo Duve
4 min readJun 17, 2024

--

Hello, fam! Its Thabhelo. So I thought of shipping my latest project, novision. I just decided it would be cool to have my personal platform to organize my coding tasks and track learning progress on Apple Vision Pro and I imagined how it would be cool to see it on that huge scale, and to also be able to use voice commands. So I started on novision. But along the way, I thought of something much bigger.

My Idea: From Task Manager to Health Monitor
Initially, I wanted a sleek, efficient way to manage my coding tasks with voice recognition. As I developed the app, I realized its potential for something much greater. The goal for v1 is now to enable administrators to manage health metrics dashboards for multiple users, potentially serving industries like aviation, sport, education, etc.

A screenshot of Thabhelo’s (my) website viewed from a task from novision on Vision Pro

Okay so we will peak into novision in this article with a breakdown of the technical steps and some code snippets:

Models and SwiftData Integration
I used SwiftData to model my app’s data. Here’s a snippet:

import Foundation
import SwiftData

@Model
class TodoList {
var title: String
//@Relationship(.cascade) var items: [TodoItem] = [] @Relationship(deleteRule: .cascade)

init(title: String) {
self.title = title
}
}

@Model
class TodoItem {
var title: String
var isDone: Bool = false

init(title: String) {
self.title = title
}
}

If you are reading this in Jun 2024 and beyond, I think its important that I highlight that as at Jun 2024, `@Relationship(.cascade)` is now deprecated, but the documentation the way the documentation was updated is a little bit confusing. (see commented line above).
But bottomline: There should be an argument label deleteRule:instead. Without an argument label, the compiler will pass.cascade to the options parameter.

You can read through the signature of the macro in the documentation:

macro Relationship(
_ options: Schema.Relationship.Option...,
deleteRule: Schema.Relationship.DeleteRule = .nullify,
minimumModelCount: Int? = 0,
maximumModelCount: Int? = 0,
originalName: String? = nil,
inverse: AnyKeyPath? = nil,
hashModifier: String? = nil
)

User Interface with SwiftUI
SwiftUI powers the UI of novision. The main view uses a grid layout to display dashboard cards, making it visually appealing and user-friendly. I also used the withAnimation function to wrap around code that makes changes to views.

Thabhelo’s card generated by voice command

DashboardCardView
Each dashboard card is represented by `DashboardCardView`:

import SwiftUI

struct DashboardCardView: View {
var list: TodoList

var body: some View {
VStack {
Text(list.title)
.font(.headline)
.padding()
Spacer()
}
.frame(width: 150, height: 150)
.background(Color.blue.opacity(0.1))
.cornerRadius(10)
.shadow(radius: 5)
}
}

ContentView
The main view arranges these cards in a grid:

import SwiftUI

struct ContentView: View {
@State private var todoLists: [TodoList] = []
@State private var selectedTodoList: TodoList? = nil

let columns = [
GridItem(.adaptive(minimum: 150))
]

var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(todoLists) { list in
Button(action: {
selectedTodoList = list
}) {
DashboardCardView(list: list)
}
}
}
.padding()
}
.navigationTitle(“Todo Lists”)
.toolbar {
Button(“Add”) {
let list = TodoList(title: “List \(todoLists.count)”, items: [])
todoLists.append(list)
}
}

if let selectedTodoList = selectedTodoList {
TodoListView(list: selectedTodoList)
} else {
Text(“Select a list”)
.font(.largeTitle)
.foregroundColor(.gray)
}
}
}
}

TodoListView
The `TodoListView` displays the details of each dashboard:

import SwiftUI

struct TodoListView: View {
@State var list: TodoList
@State private var showAddTodoAlert: Bool = false
@State private var newTodoTitle: String = “”

var body: some View {
List(list.items) { item in
HStack {
Button(action: {
if let itemIndex = list.items.firstIndex(where: { $0.id == item.id }) {
list.items[itemIndex].isDone.toggle()
}
}) {
Image(systemName: item.isDone ? “circle.fill” : “circle”)
}
Text(item.title)
Spacer()
}
}
.navigationTitle(“Task List”)
.id(list.id)
.toolbar {
Button(“Add Task”) {
showAddTodoAlert.toggle()
}
}
.alert(“Add task”, isPresented: $showAddTodoAlert) {
TextField(“Todo List Title”, text: $newTodoTitle)
Button(“Create”) {
let todo = TodoItem(title: newTodoTitle)
list.items.append(todo)
}
Button(“Cancel”, role: .cancel, action: {})
}
}
}

Scaling Up: This is my Vision for Version 1. (pun intended)
The next aim now is transforming it into a multi-dashboard health monitor. Imagine administrators managing health metrics for users in various industries, starting with aviation. This functionality will help ensure safety and efficiency by providing comprehensive health monitoring of each member.

Conclusion
Okay, this has been fun. I’m excited about the potential and eager to continue improving and scaling this app. Your feedback and suggestions are welcome as we move forward. Stay tuned for more updates!

— -

Check out the code on [GitHub](https://github.com/Thabhelo/novision) and feel free to contribute or suggest improvements. Thank you for reading to the end. If you are a student, some are saying CS is cooked, do yo agree?😂💀 but all the best!

--

--

Thabhelo Duve

I'm just a CS college student fighting for his life :-) I write about coding and tech.