top | item 43959884

(no title)

bound008 | 9 months ago

I built a simple SwiftUI/Swift Data app to do the same thing across my Apple Watch, iPhone, iPad and Desktop.

With the heavy lifting of SwiftUI/Swift Data, and iCloud providing automatic and private syncing, this is the cloc output for my project, (including widgets and all of the code and projects needed to target all of these platforms.)

-------------------------------------------------------------------------------

Language files blank comment code

-------------------------------------------------------------------------------

XML 13 0 0 579

Swift 19 131 142 548

JSON 4 0 0 115

YAML 1 7 0 43

-------------------------------------------------------------------------------

SUM: 37 138 142 1285

-------------------------------------------------------------------------------

If you live in the apple ecosystem and want to make a simple tool for yourself, you really should go ahead and do that.

It started as a desire to have a "focus" on my Apple Watch at all times, and in less than 10 hours, I have widgets, shortcuts (and Siri) integrations, and syncing across every apple platform (although I haven't yet tried it on tvOS).

I've thought about productizing it, and I might one day, but that would add orders of magnitude to the time of making this something that people should be asked to pay for.

And I'm not going to open source it, because it is ~500 loc, with no libraries plus a bunch of Xcode generated stuff.

discuss

order

rcarmo|9 months ago

You could post a gist of it, though. I’d love to do the same thing.

bound008|9 months ago

I might do that at some point... this is the main part of it, just a swift data model and one file of views. Plus a bunch of example code for making widgets work.

``` import Foundation import SwiftData

@Model final class FocusItem { let created: Date = Date() var completed: Date? var theFocus: String = "New Focus" var details: String?

    init(completed: Date? = nil, theFocus: String, details: String? = nil) {
        self.completed = completed
        self.theFocus = theFocus
        self.details = details
    }
}

struct FocusItemDescriptors { static let currentFocusPredicate = #Predicate<FocusItem> { $0.completed == nil }

    static let sortDescriptor = SortDescriptor(\FocusItem.created, order: .reverse)

    static let currentFocusFetchDescriptor = FetchDescriptor(
        predicate: currentFocusPredicate, sortBy: [sortDescriptor])
} ```

``` import SwiftData import SwiftUI import WidgetKit

struct ContentView: View { @Query( filter: FocusItemDescriptors.currentFocusPredicate, sort: [FocusItemDescriptors.sortDescriptor]) private var items: [FocusItem] @Environment(\.modelContext) private var modelContext

  @State private var isAddingNewItem = false
  @State private var newFocusText = ""

  var body: some View {
    NavigationStack {
      List {
        ForEach(items) { item in
          NavigationLink {
            FocusItemDetailView(item: item)
          } label: {
            Text(item.theFocus)
          }
        }
        .onDelete(perform: deleteItems)
      }
      .navigationTitle("Focus")
      .toolbar {
        #if os(iOS)
          ToolbarItem(placement: .navigationBarTrailing) {
            EditButton()
          }
        #endif
        ToolbarItem {
          Button(action: addItem) {
            Label("Add Item", systemImage: "plus")
          }
        }
      }
    }
    .sheet(isPresented: $isAddingNewItem) {
      AddFocusItemView(isPresented: $isAddingNewItem, addItem: addNewItemWithFocus)
    }
  }

  private func addItem() {
    isAddingNewItem = true
  }

  private func addNewItemWithFocus(_ focus: String) {
    withAnimation {
      let newItem = FocusItem(theFocus: focus)
      modelContext.insert(newItem)
      DataManager.shared.reloadWidgets()
    }
  }

  private func deleteItems(offsets: IndexSet) {
    withAnimation {
      for index in offsets {
        modelContext.delete(items[index])
      }
      DataManager.shared.reloadWidgets()
    }
  }
}

struct FocusItemDetailView: View { @Environment(\.dismiss) private var dismiss let item: FocusItem

  var body: some View {
    VStack {
      Text(item.theFocus)
      if let details = item.details {
        Text(details)
      }
      Text(
        "\(item.created, format: Date.FormatStyle(date: .numeric, time: .standard))"
      )
      Button {
        item.completed = Date()
        DataManager.shared.reloadWidgets()
        dismiss()
      } label: {
        Text("Mark as Complete")
      }
    }
  }
} struct AddFocusItemView: View { @Binding var isPresented: Bool let addItem: (String) -> Void @State private var newFocusText = ""

  var body: some View {
    NavigationView {
      Form {
        TextField("What is your focus?", text: $newFocusText, axis: .vertical)
          .lineLimit(3...10)
      }
      .navigationTitle("New Focus")
      .toolbar {
        ToolbarItem(placement: .cancellationAction) {
          Button("Cancel") {
            isPresented = false
          }
        }
        ToolbarItem(placement: .confirmationAction) {
          Button("Add") {
            addItem(newFocusText)
            isPresented = false
          }
          .disabled(newFocusText.isEmpty)
        }
      }
    }
  }
```