Skip to content

Instantly share code, notes, and snippets.

@lanserxt
Created July 31, 2026 08:38
Show Gist options
  • Select an option

  • Save lanserxt/d9c9a8d896cf0fee183250df973f74d8 to your computer and use it in GitHub Desktop.

Select an option

Save lanserxt/d9c9a8d896cf0fee183250df973f74d8 to your computer and use it in GitHub Desktop.
iOS 27: Suggested Actions for chat messages with dates and locations
//
// ChatMessage.swift
// WWDC25Demo
//
// Created by Anton Gubarenko on 22.07.2026.
//
import Foundation
import SwiftUI
import SuggestedActions
struct ChatMessage: Identifiable {
let id: UUID
let sender: Participant
let text: String
let imageName: String?
let cinemaLocation: CinemaLocation?
let date: Date
struct Participant: Hashable {
let name: String
let handle: String
let isCurrentUser: Bool
}
struct CinemaLocation: Hashable {
let name: String
let address: String
let distance: String
let showtime: String
}
@available(iOS 27.0, *)
var suggestedActionsMessage: SuggestedActionsMessage {
SuggestedActionsMessage(
id: id,
date: date,
subject: nil,
body: AttributedString(text),
sender: .init(
name: sender.name,
handle: sender.handle,
isUser: sender.isCurrentUser
),
recipients: []
)
}
}
// MARK: - Demo Data
extension ChatMessage {
static let anton = Participant(
name: "Anton",
handle: "anton@example.com",
isCurrentUser: true
)
static let maya = Participant(
name: "Maya",
handle: "maya@example.com",
isCurrentUser: false
)
static let demo: [ChatMessage] = [
ChatMessage(
id: UUID(),
sender: maya,
text: "These are the movies showing this weekend.",
imageName: "movie-posters",
cinemaLocation: nil,
date: .now.addingTimeInterval(-300)
),
ChatMessage(
id: UUID(),
sender: anton,
text: "Let’s watch The Last Horizon on Saturday.",
imageName: nil,
cinemaLocation: nil,
date: .now.addingTimeInterval(-240)
),
ChatMessage(
id: UUID(),
sender: maya,
text: "The 19:30 screening at Central Cinema works for me.",
imageName: nil,
cinemaLocation: CinemaLocation(
name: "Central Cinema",
address: "18 Market Street",
distance: "1.2 km away",
showtime: "Saturday, 19:30"
),
date: .now.addingTimeInterval(-180)
),
ChatMessage(
id: UUID(),
sender: anton,
text: "Great. Let’s meet there. Will add to notes to bring your favourite popcorn!",
imageName: nil,
cinemaLocation: nil,
date: .now.addingTimeInterval(-120)
)
]
}
// MARK: - Message Cell
struct MessageCell: View {
let message: ChatMessage
let previousMessages: [ChatMessage]
var body: some View {
VStack(
alignment: message.sender.isCurrentUser
? .trailing
: .leading,
spacing: 8
) {
if let imageName = message.imageName {
Image(imageName)
.resizable()
.scaledToFill()
.frame(width: 240, height: 150)
.clipShape(
RoundedRectangle(cornerRadius: 18)
)
}
Text(message.text)
.padding(.horizontal, 14)
.padding(.vertical, 10)
.background(
message.sender.isCurrentUser
? Color.accentColor
: Color.secondary.opacity(0.15)
)
.foregroundStyle(
message.sender.isCurrentUser
? .white
: .primary
)
.clipShape(
RoundedRectangle(cornerRadius: 18)
)
if let cinemaLocation = message.cinemaLocation {
CinemaLocationCell(
location: cinemaLocation,
isCurrentUser: message.sender.isCurrentUser
)
}
if #available(iOS 27.0, *) {
SuggestedActionsView(
message: message.suggestedActionsMessage,
previousMessages: previousMessages
.suffix(
SuggestedActionsMessage.previousMessagesLimit
)
.map(\.suggestedActionsMessage)
)
.buttonBorderShape(.capsule)
.font(.callout)
}
}
.frame(
maxWidth: .infinity,
alignment: message.sender.isCurrentUser
? .trailing
: .leading
)
}
}
private struct CinemaLocationCell: View {
let location: ChatMessage.CinemaLocation
let isCurrentUser: Bool
var body: some View {
HStack(alignment: .top, spacing: 12) {
Image(systemName: "popcorn.fill")
.font(.title3)
.foregroundStyle(.white)
.frame(width: 42, height: 42)
.background(Color.red)
.clipShape(RoundedRectangle(cornerRadius: 12))
VStack(alignment: .leading, spacing: 6) {
Text(location.name)
.font(.headline)
Text(location.address)
.font(.subheadline)
.foregroundStyle(.secondary)
HStack(spacing: 12) {
Label(location.showtime, systemImage: "clock")
Label(location.distance, systemImage: "location")
}
.font(.caption)
.foregroundStyle(.secondary)
}
}
.padding(12)
.frame(width: 280, alignment: .leading)
.background(
isCurrentUser
? Color.accentColor.opacity(0.16)
: Color.secondary.opacity(0.12)
)
.clipShape(RoundedRectangle(cornerRadius: 18))
}
}
// MARK: - Chat Screen
struct MovieChatView: View {
private let messages = ChatMessage.demo
var body: some View {
NavigationStack {
ScrollView {
LazyVStack(spacing: 16) {
ForEach(
Array(messages.enumerated()),
id: \.element.id
) { index, message in
MessageCell(
message: message,
previousMessages: Array(
messages.prefix(index)
)
)
}
}
.padding()
}
.navigationTitle("Movie Night Chat")
}
}
}
#Preview {
MovieChatView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment