SwiftUI DynamicIsland View Not Updating on AppIntent

I’m working on a SwiftUI app that uses the DynamicIsland’s expanded region to display a moving rectangular object. The movement is triggered by an AppIntent, but I’m encountering an issue where the UI doesn’t update to reflect the object’s new position when the intent is triggered:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>import SwiftUI
import WidgetKit
import AppIntents
import ActivityKit
struct RectangularObjectMovingView: View, ActivityAttributes {
public typealias ContentState = MovementState
public var id: String
let activityID: String
public struct MovementState: Codable, Hashable {
var position: CGFloat
var isMoving: Bool
var gameState: GameState
}
@State private var objectY: CGFloat
@State private var gameState: GameState
var body: some View {
GeometryReader { geometry in
ZStack {
// Moving object
Rectangle()
.fill(Color.white)
.frame(width: 40, height: 40)
.position(x: 40, y: geometry.size.height - 20 - objectY)
.animation(.easeInOut(duration: 0.5), value: objectY)
}
.overlay(
Button(intent: MoveRectangularObjectIntent(activityID: activityID)) {
Color.blue
} // Passing activityID to Intent will not let user return back to main app immediately when on press of this button
.buttonStyle(PlainButtonStyle())
)
}
}
static func handleInput(for activityID: String) {
guard let activity = Activity<RectangularObjectMovingView>.activities.first else {
return
}
Task {
var newState = activity.content.state
newState.isMoving = true
newState.position = 100 // Move to 100
await activity.update(ActivityContent(state: newState, staleDate: nil))
// Simulate movement timing
try? await Task.sleep(nanoseconds: UInt64(0.5 * Double(NSEC_PER_SEC)))
newState.isMoving = false
newState.position = 0 // Move back to 0
await activity.update(ActivityContent(state: newState, staleDate: nil))
}
}
}
struct MoveRectangularObjectIntent: AppIntent {
static var title: LocalizedStringResource = "Move Object Signal"
static var description = IntentDescription("Sends a movement signal to the object")
@Parameter(title: "Activity ID")
var activityID: String
func perform() async throws -> some IntentResult {
print("MoveRectangularObjectIntent performed for activity ID: (activityID)")
RectangularObjectMovingView.handleInput(for: activityID)
return .result()
}
}
enum ObjectState: String, Codable {
case idle
case moving
case stopped
}
class ActivityManager {
static func startActivity(position: CGFloat = 0, isMoving: Bool = false, state: ObjectState = .idle) async throws -> String {
let id = UUID().uuidString
let activityID = UUID().uuidString
let attributes = RectangularObjectMovingView(id: id, activityID: activityID)
let initialContent = ActivityContent(state: RectangularObjectMovingView.ContentState(position: position, isMoving: isMoving, state: state), staleDate: nil)
let activity = try await Activity.request(
attributes: attributes,
content: initialContent,
pushType: nil
)
return activity.id
}
static func endAllActivities() async {
for activity in Activity<RectangularObjectMovingView>.activities {
await activity.end(nil, dismissalPolicy: .immediate)
}
}
static func signalInput(id: String) async {
if let activity = Activity<RectangularObjectMovingView>.activities.first(where: { $0.id == id }) {
let currentState = activity.content.state
let updatedContent = ActivityContent(state: RectangularObjectMovingView.ContentState(
position: currentState.position,
isMoving: true,
state: .moving
), staleDate: nil)
await activity.update(updatedContent)
}
}
}
</code>
<code>import SwiftUI import WidgetKit import AppIntents import ActivityKit struct RectangularObjectMovingView: View, ActivityAttributes { public typealias ContentState = MovementState public var id: String let activityID: String public struct MovementState: Codable, Hashable { var position: CGFloat var isMoving: Bool var gameState: GameState } @State private var objectY: CGFloat @State private var gameState: GameState var body: some View { GeometryReader { geometry in ZStack { // Moving object Rectangle() .fill(Color.white) .frame(width: 40, height: 40) .position(x: 40, y: geometry.size.height - 20 - objectY) .animation(.easeInOut(duration: 0.5), value: objectY) } .overlay( Button(intent: MoveRectangularObjectIntent(activityID: activityID)) { Color.blue } // Passing activityID to Intent will not let user return back to main app immediately when on press of this button .buttonStyle(PlainButtonStyle()) ) } } static func handleInput(for activityID: String) { guard let activity = Activity<RectangularObjectMovingView>.activities.first else { return } Task { var newState = activity.content.state newState.isMoving = true newState.position = 100 // Move to 100 await activity.update(ActivityContent(state: newState, staleDate: nil)) // Simulate movement timing try? await Task.sleep(nanoseconds: UInt64(0.5 * Double(NSEC_PER_SEC))) newState.isMoving = false newState.position = 0 // Move back to 0 await activity.update(ActivityContent(state: newState, staleDate: nil)) } } } struct MoveRectangularObjectIntent: AppIntent { static var title: LocalizedStringResource = "Move Object Signal" static var description = IntentDescription("Sends a movement signal to the object") @Parameter(title: "Activity ID") var activityID: String func perform() async throws -> some IntentResult { print("MoveRectangularObjectIntent performed for activity ID: (activityID)") RectangularObjectMovingView.handleInput(for: activityID) return .result() } } enum ObjectState: String, Codable { case idle case moving case stopped } class ActivityManager { static func startActivity(position: CGFloat = 0, isMoving: Bool = false, state: ObjectState = .idle) async throws -> String { let id = UUID().uuidString let activityID = UUID().uuidString let attributes = RectangularObjectMovingView(id: id, activityID: activityID) let initialContent = ActivityContent(state: RectangularObjectMovingView.ContentState(position: position, isMoving: isMoving, state: state), staleDate: nil) let activity = try await Activity.request( attributes: attributes, content: initialContent, pushType: nil ) return activity.id } static func endAllActivities() async { for activity in Activity<RectangularObjectMovingView>.activities { await activity.end(nil, dismissalPolicy: .immediate) } } static func signalInput(id: String) async { if let activity = Activity<RectangularObjectMovingView>.activities.first(where: { $0.id == id }) { let currentState = activity.content.state let updatedContent = ActivityContent(state: RectangularObjectMovingView.ContentState( position: currentState.position, isMoving: true, state: .moving ), staleDate: nil) await activity.update(updatedContent) } } } </code>
import SwiftUI
import WidgetKit
import AppIntents
import ActivityKit

struct RectangularObjectMovingView: View, ActivityAttributes {
    public typealias ContentState = MovementState
    
    public var id: String
    let activityID: String
    
    public struct MovementState: Codable, Hashable {
        var position: CGFloat
        var isMoving: Bool
        var gameState: GameState
    }
    
    @State private var objectY: CGFloat
    @State private var gameState: GameState
    
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                // Moving object
                Rectangle()
                    .fill(Color.white)
                    .frame(width: 40, height: 40)
                    .position(x: 40, y: geometry.size.height - 20 - objectY)
                    .animation(.easeInOut(duration: 0.5), value: objectY)
                
            }
            .overlay(
                Button(intent: MoveRectangularObjectIntent(activityID: activityID)) {
                    Color.blue
                } // Passing activityID to Intent will not let user return back to main app immediately when on press of this button
                .buttonStyle(PlainButtonStyle())
            )
        }
    }
    
    static func handleInput(for activityID: String) {
        guard let activity = Activity<RectangularObjectMovingView>.activities.first else {
            return
        }
        
        Task {
            var newState = activity.content.state
            newState.isMoving = true
            newState.position = 100 // Move to 100
            await activity.update(ActivityContent(state: newState, staleDate: nil))
            
            // Simulate movement timing
            try? await Task.sleep(nanoseconds: UInt64(0.5 * Double(NSEC_PER_SEC)))
            
            newState.isMoving = false
            newState.position = 0 // Move back to 0
            await activity.update(ActivityContent(state: newState, staleDate: nil))
        }
    }
}

struct MoveRectangularObjectIntent: AppIntent {
    static var title: LocalizedStringResource = "Move Object Signal"
    static var description = IntentDescription("Sends a movement signal to the object")
    
    @Parameter(title: "Activity ID")
    var activityID: String
    
    func perform() async throws -> some IntentResult {
        print("MoveRectangularObjectIntent performed for activity ID: (activityID)")
        RectangularObjectMovingView.handleInput(for: activityID)
        return .result()
    }
}

enum ObjectState: String, Codable {
    case idle
    case moving
    case stopped
}

class ActivityManager {
    static func startActivity(position: CGFloat = 0, isMoving: Bool = false, state: ObjectState = .idle) async throws -> String {
        let id = UUID().uuidString
        let activityID = UUID().uuidString
        let attributes = RectangularObjectMovingView(id: id, activityID: activityID)
        let initialContent = ActivityContent(state: RectangularObjectMovingView.ContentState(position: position, isMoving: isMoving, state: state), staleDate: nil)
        
        let activity = try await Activity.request(
            attributes: attributes,
            content: initialContent,
            pushType: nil
        )
        
        return activity.id
    }
    
    static func endAllActivities() async {
        for activity in Activity<RectangularObjectMovingView>.activities {
            await activity.end(nil, dismissalPolicy: .immediate)
        }
    }
    
    static func signalInput(id: String) async {
        if let activity = Activity<RectangularObjectMovingView>.activities.first(where: { $0.id == id }) {
            let currentState = activity.content.state
            let updatedContent = ActivityContent(state: RectangularObjectMovingView.ContentState(
                position: currentState.position,
                isMoving: true,
                state: .moving
            ), staleDate: nil)
            
            await activity.update(updatedContent)
        }
    }
}

When the MoveRectangularObjectIntent is triggered (by tapping the overlay button in the DynamicIsland), the handleInput method is called, and it updates the activity’s state. However, this state change doesn’t seem to be reflected in the UI – the rectangular object doesn’t move.

I’ve confirmed that the intent is being triggered and the handleInput method is being called, but the visual update isn’t happening. One side note thing worth mentioning is that the activityID passed to the intent doesn’t match the ID of the activity being updated, the handleInput method is still able to change the state (e.g., from idle to moving):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>MoveRectangularObjectIntent performed for activity ID: 47BCB9B5-D773-44E8-B46A-82BD130EE95E
Updating content for activity 4088E571-4F10-4BAD-B45A-76BCE530A95A
</code>
<code>MoveRectangularObjectIntent performed for activity ID: 47BCB9B5-D773-44E8-B46A-82BD130EE95E Updating content for activity 4088E571-4F10-4BAD-B45A-76BCE530A95A </code>
MoveRectangularObjectIntent performed for activity ID: 47BCB9B5-D773-44E8-B46A-82BD130EE95E
Updating content for activity 4088E571-4F10-4BAD-B45A-76BCE530A95A

This suggests that the activityID matching might not be crucial for updating the view state. However, this begs the question: Why isn’t the UI updating to reflect the state changes (such that the rectangular object moves in the DynamicIsland view?), even though the handleInput method is successfully changing the state? I’ve tried using @State variables to trigger redraws, but it doesn’t seem to help either. This doesn’t seem to be a DynamicIsland specific problem, but that some part of my SwiftUI was not working. What is the best approach to this problem?

Any insights or suggestions would be greatly appreciated!

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật