Pattern · AI Chat
NewChat History SwiftUI Block
Past assistant chats: live search, Pinned and date groups, swipe and context actions, inline rename and delete that confirms.
The list of past conversations. A search field filters by title and last line as you type, and chats sit under Pinned, Today, Yesterday, Previous 7 days and Earlier. Swipe a row for Pin and Delete or hold it for Rename, Pin, Share and Delete; rename edits the title in place, and delete confirms, fades the row while your closure runs and restores it with the error if it fails.
- SwiftUI
- iOS 17.0+
- chat-history
$199 once, every piece included. See how installing works · Sign in
01
What's included
SwiftUI source you own, built on Apple frameworks only.
- Live search by title and last line, with a no-results state
- Pinned, Today, Yesterday, Previous 7 days and Earlier
- Swipe for Pin and Delete, or hold for Rename, Pin, Share and Delete
- Rename in place; Delete confirms, fades the row, and restores it on failure
- No chats yet with New chat, and ghost rows while loading
02
Implementation notes
How it behaves, what it handles, and the decisions behind it.
A search field filters the list as you type, by title and by last line, and says so when nothing matches. Pinned chats come first, then Today, Yesterday, Previous 7 days and Earlier, each a card of rows with the title, a one-line snippet, when the chat was last active and, when pinned, a pin. Swipe a row left for Pin and Delete, or press and hold it for Rename, Pin, Share and Delete. Pin moves the row into Pinned at once and runs your closure; Rename turns the title into a field in place and saves it when you press Done; Delete asks first, then fades the row while your closure runs and removes it, or brings it back with the error under it if it throws. With no chats at all the block shows No chats yet and New chat, and isLoading draws grey rows in the list's layout.
Notes
ChatHistoryBlock(conversations:isLoading:onOpen:onNewChat:onRename:onPin:onDelete:)takes a binding to every chat, in any order. It is a section: no navigation stack, background, padding or scroll view of its own.ChatHistoryBlock.Conversationhas anid,title,snippet(the last line),updated(sets its group and time),isPinnedand an optionalshareURL, which adds Share to the menu.Conversation.sampleis the nine chats from the preview.onOpenruns when a row is tapped. A tap on any row while one is swiped open closes it instead.onRenamesaves a new title. The title changes at once; throw and the old title comes back with the error under the row. Empty or unchanged titles are ignored.onPinsaves a pin change after the row has moved. Throw to move it back.onDeleteruns after the person confirms in a dialog ("This can't be undone."). The row fades to 40% with a spinner until it returns, then leaves the list. Throw to bring it back with the error'serrorDescription, or "Couldn't delete this chat. Try again." Leave it out to hide Delete everywhere.onNewChatstarts a chat from the empty state. Leave it out to hide New chat.- Times read the time for today and yesterday, the weekday within a week, else the date.
- Haptics mark a pin, a finished delete and any failure.
- VoiceOver reads each row as its title (with "pinned") and its time and snippet, offers Pin, Rename and Delete as actions, and announces results. Rows grow with Dynamic Type.
- Reduce Motion turns the swipe and every move into short fades.
ChatHistoryBlockExampleat the end ofChatHistoryBlock.swiftis the Chats screen from the preview, with stubs that take a moment and succeed. PasswrapsInNavigationStack: falseto push it in your own stack.
Theming
- Reads
SPThemefrom@Environment(\.spTheme)and requires thedesign-systemitem. - The search field is 52 points on
fillatradius.mdwith aseparatorstroke that turnstextPrimarywhen focused. - Group titles are
type.subheadsemibold intextSecondary. Cards arecolor.surfaceatradius.lgwithSPHairlinedividers inset to the text; groups sit 24 points apart. - Titles are
type.bodyintextPrimary, snippetstype.subheadintextSecondary, timestype.footnoteand the pintype.captionintextTertiary. - Swipe actions are
neutral(Pin) anddanger(Delete) withstatusOnlabels. Errors aredanger. New chat in the empty state is the one accent. - Moves use
motion.smoothand pressesmotion.snappy.
03
Keep building
Pieces that work well next to this one.