Exploring Journaling Suggestions: Adding Reflection Prompt
I am working on a journaling app for myself, Harmony, and started implementing Journaling Suggestions framework into it. I want to add more depth to it and this year, Apple made the Reflection prompt available to developers outside of their own Journal app.
Reflection Prompt
What is a Reflection prompt? It is a suggestion that encourages users to pause and think deeply about a particular topic or question. I can filter through the prompts given by the system, and reflect upon them according to the moment.
Understanding the JournalingSuggestion.Reflection Structure
The JournalingSuggestion.Reflection
structure, available in iOS 18.0+ (currently in beta), represents this prompt in code:
/// A suggestion for a reflection prompt.
///
/// The system provides an instance of this structure to your app when a person chooses a reflection prompt suggestion
/// in the ``JournalingSuggestionsPicker``.
@available(iOS 18.0, *)
public struct Reflection : JournalingSuggestionAsset { }
This structure has two key properties:
color
: An optionalColor
that sets the background for the prompt in the picker.prompt
: AString
containing the actual reflection question or statement.
The system provides an array of this structure when the user selects a reflection prompt from the JournalingSuggestionsPicker
.
Implementing Reflection Prompts
Here is a simple implementation for getting started with the Reflection
prompts. We start by importing the required modules:
import JournalingSuggestions
import SwiftUI
The ReflectionView
is our main view:
@available(iOS 18.0, *)
struct ReflectionView: View {
@State private var reflectionSuggestions: [JournalingSuggestion.Reflection] = []
@State private var suggestionTitle: String?
var body: some View {
NavigationStack {
VStack {
ForEach($reflectionSuggestions, content: ReflectionPromptView.init)
JournalingSuggestionsPicker {
Text("Get Reflection.")
} onCompletion: { suggestion in
suggestionTitle = suggestion.title
reflectionSuggestions = await suggestion.content(forType: JournalingSuggestion.Reflection.self)
}
.buttonStyle(.borderedProminent)
}
.navigationTitle(suggestionTitle ?? "")
}
}
}
This view includes a JournalingSuggestionsPicker
to fetch new prompts and a ForEach
loop to display existing prompts using the ReflectionPromptView
.
To use Reflection
in SwiftUI's ForEach, we need to make it conform to the Identifiable
protocol:
@available(iOS 18.0, *)
extension JournalingSuggestion.Reflection: @retroactive Identifiable {
public var id: String {
self.prompt
}
}
The ReflectionPromptView
struct is responsible for displaying individual reflection prompts:
@available(iOS 18.0, *)
struct ReflectionPromptView: View {
@Binding var suggestion: JournalingSuggestion.Reflection
var body: some View {
let reflectionSuggestion = $suggestion.wrappedValue
let backgroundColor = reflectionSuggestion.color ?? .white
Text(reflectionSuggestion.prompt)
.font(.title)
.bold()
.multilineTextAlignment(.center)
.foregroundStyle(foregroundColor(for: backgroundColor))
.frame(maxHeight: .infinity)
.padding()
.background(backgroundColor, in: RoundedRectangle(cornerRadius: 16))
.padding()
}
private func foregroundColor(for backgroundColor: Color) -> Color {
var whiteValue = CGFloat()
if UIColor(backgroundColor).getWhite(&whiteValue, alpha: nil) {
return whiteValue > 0.6 ? .black : .white
}
return .white
}
}
This view handles the display of each prompt, including text styling and background color. The foregroundColor
function ensures the text is always readable against the background.
Here's the complete code for implementing Reflection prompts:
@available(iOS 18.0, *)
struct ReflectionView: View {
@State private var reflectionSuggestions: [JournalingSuggestion.Reflection] = []
@State private var suggestionTitle: String?
var body: some View {
NavigationStack {
VStack {
ForEach($reflectionSuggestions, content: ReflectionPromptView.init)
JournalingSuggestionsPicker {
Text("Get Reflection.")
} onCompletion: { suggestion in
suggestionTitle = suggestion.title
reflectionSuggestions = await suggestion.content(forType: JournalingSuggestion.Reflection.self)
}
.buttonStyle(.borderedProminent)
}
.navigationTitle(suggestionTitle ?? "")
}
}
}
@available(iOS 18.0, *)
extension JournalingSuggestion.Reflection: @retroactive Identifiable {
public var id: String {
self.prompt
}
}
@available(iOS 18.0, *)
struct ReflectionPromptView: View {
@Binding var suggestion: JournalingSuggestion.Reflection
var body: some View {
let reflectionSuggestion = $suggestion.wrappedValue
let backgroundColor = reflectionSuggestion.color ?? .white
Text(reflectionSuggestion.prompt)
.font(.title)
.bold()
.multilineTextAlignment(.center)
.foregroundStyle(foregroundColor(for: backgroundColor))
.frame(maxHeight: .infinity)
.padding()
.background(backgroundColor, in: RoundedRectangle(cornerRadius: 16))
.padding()
}
private func foregroundColor(for backgroundColor: Color) -> Color {
var whiteValue = CGFloat()
if UIColor(backgroundColor).getWhite(&whiteValue, alpha: nil) {
return whiteValue > 0.6 ? .black : .white
}
return .white
}
}
How It Works
When the user taps Get Reflection., the system presents a picker interface like the one I have in the image shown before. Once a prompt is selected, the onCompletion
closure is called with a JournalingSuggestion
object. This object contains several pieces of information, including the actual reflection prompt.
Here's what the raw JournalingSuggestion
object looks like when printed to the console:
JournalingSuggestion(items: [JournalingSuggestions.JournalingSuggestion.ItemContent(id: 6B18D50A-AA8F-4BEB-814D-182D3E5EE6BA, representations: [JournalingSuggestions.JournalingSuggestion.Reflection], content: JournalingSuggestions.InternalAssetContent(providers: [JournalingSuggestions.InternalAssetContent.AssetProvider(type: JournalingSuggestions.JournalingSuggestion.Reflection, loader: (Function))]))], title: "Gratitude Reflection", date: Optional(2024-07-07 04:59:47 +0000 to 2024-07-07 05:59:47 +0000), suggestionIdentifier: F3B97E7C-6304-46F1-A0DB-9DD58BF597B8, suggestionHashValue: 12949094976)
The important part is the ItemContent
, which contains the Reflection
prompt. I access this with:
reflectionSuggestions = await suggestion.content(forType: JournalingSuggestion.Reflection.self)
Running the app:
Conclusion
I am excited about the possibilities Reflection
prompts bring to journaling apps. I have been thinking about how I can use LLM APIs to reflect further, but that's for another day.
Remember, this feature is still in beta for iOS 18.0. Keep an eye on Apple's documentation for any changes or updates as we approach the official release in September. Happy coding and journaling!