Replies: 3 comments
-
Thanks for your proposal, @ziadtamim! I think users of ConversationKit shouldn't have to concern themselves with having to set up the message composer view - that should be an implementation detail they don't have to care about. I'm also not convinced that SwiftUI doesn't use the environment (or preferences) for registering views. Toolbar is a good example. This code snippet registers three toolbar items, one on the public var body: some View {
ZStack(alignment: .bottom) {
ScrollView {
LazyVStack(spacing: 20) {
ForEach(messages) { message in
content(message)
.padding(.horizontal)
}
Spacer()
.frame(height: 50)
.toolbar {
Button(action: {}) {
Text("Three")
}
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.scrollBounceBehavior(.always)
.scrollDismissesKeyboard(.interactively)
.scrollPosition(id: $scrolledID, anchor: .top)
.toolbar {
Button(action: {}) {
Text("One")
}
}
MessageComposerView(message: $message)
.padding(.bottom, 10) // keep distance from keyboard
.focused($focusedField, equals: .message)
.onSubmitAction {
submit()
}
}
.onChange(of: messages) { oldValue, newValue in
scrolledID = messages.last?.id
}
.toolbar {
Button(action: {}) {
Text("Two")
}
}
} The end result: a toolbar with the two items we registered. ![]() Given that a toolbar is a UI element that will be shown on a container, the actual implementation probably makes use of SwiftUI's preference system, but I am pretty sure they inject the view either into the environment or into the preferences. |
Beta Was this translation helpful? Give feedback.
-
@peterfriese The For that reason, using the type eraser On the other hand, I’m not entirely sure how the toolbar is designed or if the compiler somehow generate some extra wrappers to hold the result builder while preserving the required types. It’s also possible that Apple uses type erasers in those cases, treating it as technical debt to be addressed later on as the language evolves around opaque types with its current limitations. |
Beta Was this translation helpful? Give feedback.
-
@peterfriese Turns out I was mistaken earlier, iOS 18+ Apple introduced an API that let us avoid Here’s the updated code I’ve tried out: extension EnvironmentValues {
@available(iOS 18.0, *)
@Entry fileprivate var attachmentActions: SubviewsCollection?
}
extension View {
@available(iOS 18.0, *)
func attachmentActions<Content: View>(@ViewBuilder content: @escaping () -> Content) -> some View {
Group(subviews: content()) { subviews in
environment(\.attachmentActions, subviews)
}
}
}
struct MessageComposerView: View {
@Environment(\.attachmentActions) private var attachmentActions
var body: some View {
if #available(iOS 26.0, *) {
GlassEffectContainer {
HStack(alignment: .bottom) {
if let attachmentActions {
Menu { attachmentActions } label: {
Image(systemName: "plus")
}
.controlSize(.large)
.buttonBorderShape(.circle)
.glassEffect(.regular.interactive(), in: .circle)
}
// ... other parts
}
}
}
}
}
`` |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Overview
This proposal to redesign the
ConversationView
to be more composable. Rather than using environment variables to inject child views, instead shift the responsibility to the parent view, enabling flexible composition.Motivation
@Environment
is intended for configuration or lightweight data—not for injecting views..sheet
,.toolbar
, etc., to manage optional views.ConversationView
becomes more flexible, and separation of concerns.Proposed Changes
ConversationList
acts as a layout container responsible for structuring the chat UI..messageComposer(...)
as a view modifier to optionally attach a composer input area.@ViewBuilder
, allowing inline customization with full control.Usage Examples
With Attachment Actions
Without Attachment Actions
Beta Was this translation helpful? Give feedback.
All reactions