Exploring Journaling Suggestions: Emotions and Moods with State of Mind

After covering Reflection Prompts, I decided to talk about State of Mind suggestion for journaling.

Exploring Journaling Suggestions: Adding Reflection Prompt
Enhance your iOS journaling app with Apple’s new Reflection prompts. Learn to implement the JournalingSuggestion Reflection, and fetch prompts using JournalingSuggestionsPicker.

Understanding JournalingSuggestion.StateOfMind

Apple introduced the JournalingSuggestion.StateOfMind structure in iOS 18.0 (currently in beta). It captures a person's emotional state in a journaling app.

Here is what the structure looks like:

/// A suggestion that describes a state of mind reflection in the Health app.
@available(iOS 18.0, *)
public struct StateOfMind : JournalingSuggestionAsset { }

This structure comes with some useful properties:

  • darkBackground: An optional Gradient for dark mode icons.
  • lightBackground: An optional Gradient for light mode icons.
  • icon: A URL to an image that represents the mood.
  • state: The actual state of mind, stored as an HKStateOfMind object.

I have already covered HKStateOfMind in detail here:

Exploring HealthKit: Working with State of Mind APIs
Learn about HealthKit’s new State of Mind APIs and the components: Kind, Valence, Labels, and Associations. Explore how to fetch, create, and save mood data using Swift Concurrency.

The system gives your app an instance of this structure when someone picks a state of mind suggestion in the JournalingSuggestionsPicker.

Implementing State of Mind Suggestions

Let's look at how you can use this in your app:

import JournalingSuggestions
import SwiftUI
import HealthKit

@available(iOS 18.0, *)
struct StateOfMindView: View {
    @State private var stateOfMind: JournalingSuggestion.StateOfMind?
    @State private var suggestionTitle: String?
    
    var body: some View {
        NavigationStack {
            VStack {
                if let stateOfMind = stateOfMind {
                    StateOfMindDisplayView(stateOfMind: stateOfMind)
                }
                
                JournalingSuggestionsPicker {
                    Text("How are you feeling?")
                } onCompletion: { suggestion in
                    suggestionTitle = suggestion.title
                    Task {
                        stateOfMind = await suggestion.content(forType: JournalingSuggestion.StateOfMind.self).first
                    }
                }
                .buttonStyle(.borderedProminent)
            }
            .navigationTitle(suggestionTitle ?? "")
        }
    }
}

This view includes a JournalingSuggestionsPicker to get new state of mind suggestions. When a user picks a suggestion, we update the stateOfMind property.

An interesting thing to note is how the system handles data from third-party apps. If a third-party app logs state of mind data, it shows up in the title. For example, you might see "Arising - Momentary Emotion" for my app called Arising that logged the mood data. This helps users understand where their mood data is coming from.

Next, let's create a view to display the state of mind related information:

import HealthKit

extension HKStateOfMind.ValenceClassification: @retroactive CustomStringConvertible {
  public var description: String {
    switch self {
      case .veryUnpleasant: "Very Unpleasant"
      case .unpleasant: "Unpleasant"
      case .slightlyUnpleasant: "Slightly Unpleasant"
      case .neutral: "Neutral"
      case .slightlyPleasant: "Slightly Pleasant"
      case .pleasant: "Pleasant"
      case .veryPleasant: "Very Pleasant"
      @unknown default: "Neautral"
    }
  }
}

@available(iOS 18.0, *)
struct StateOfMindDisplayView: View {
  let stateOfMind: JournalingSuggestion.StateOfMind

  var body: some View {
    VStack {
      if let iconURL = stateOfMind.icon {
        AsyncImage(url: iconURL) { image in
          image.resizable()
            .scaledToFit()
        } placeholder: {
          ProgressView()
        }
      }

      Text(stateOfMind.state.valenceClassification.description)
        .font(.title)
        .bold()
        .multilineTextAlignment(.center)
        .padding()
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(backgroundGradient)
  }

  private var backgroundGradient: some View {
    LinearGradient(
      gradient: stateOfMind.lightBackground ?? Gradient(colors: [.white]),
      startPoint: .topLeading,
      endPoint: .bottomTrailing
    )
  }
}

I added custom string representation for each mood state. This view shows the mood icon and uses the custom description for the state of mind.

When you tap "How are you feeling?", the system shows a picker interface. After selecting a mood, the onCompletionclosure runs with a JournalingSuggestion object. This object has the state of mind information.

Here is what a raw JournalingSuggestion object looks like:

JournalingSuggestions.JournalingSuggestion(items: [JournalingSuggestions.JournalingSuggestion.ItemContent(id: FE9C3C74-1AEA-4003-9BFD-5F0C58DC36C2, representations: [JournalingSuggestions.JournalingSuggestion.StateOfMind, SwiftUI.Image, UIImage], content: JournalingSuggestions.InternalAssetContent(providers: [JournalingSuggestions.InternalAssetContent.AssetProvider(type: JournalingSuggestions.JournalingSuggestion.StateOfMind, loader: (Function)), JournalingSuggestions.InternalAssetContent.AssetProvider(type: SwiftUI.Image, loader: (Function)), JournalingSuggestions.InternalAssetContent.AssetProvider(type: UIImage, loader: (Function))]))], title: "Arising — Momentary Emotion", date: Optional(2024-08-16 06:42:23 +0000 to 2024-08-16 06:42:23 +0000), suggestionIdentifier: AB5F68CD-D892-4873-9E24-606B3C887518, suggestionHashValue: 12941230080)

Moving Forward

It is a bit annoying that the Health app notes are constrained when you log a new entry, but makes sense that it is not a journaling app. I guess that is where this journaling suggestions fills the gap.

Remember, this feature is still in beta for iOS 18.0. Keep an eye on Apple's documentation for any changes before the official release in September.

Happy coding and journaling!