Skip to content

Commit

Permalink
Improved Safety Behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
EmilioPelaez committed Oct 18, 2023
1 parent cdb0ca1 commit 656749e
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 51 deletions.
47 changes: 11 additions & 36 deletions Example/HierarchyResponderExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,22 @@
import HierarchyResponder
import SwiftUI

// A simple event that could be triggered by a user action or another event
struct ButtonEvent: Event {}
// An error that can be displayed in an alert
struct ButtonError: AlertableError {
var title: String? { "Hello" }
var message: String { "World!" }
}
struct EventOne: Event {}
struct EventTwo: Event {}

struct ContentView: View {
@State var value: Int = 0

var body: some View {
VStack {
TriggerView()
Text("\(value)")
}
.handleEvent(ButtonEvent.self) {
try await Task.sleep(nanoseconds: 1_000_000_000)
value += 1
}
/*
`handleAlertErrors` will handle all errors that conform to `AlertableError`
and will display an alert
*/
.handleAlertErrors()
}
}

struct TriggerView: View {
var body: some View {
/*
Instead of using `Button`, we use `EventButton`, which automatically sends
a `ButtonEvent` event up the view hierarchy
*/
EventButton(ButtonEvent()) {
Text("Tap Me!")
.padding(.horizontal)
.font(.title)
}
.buttonStyle(.borderedProminent)
.tint(.blue)
EventButton("Event 1", event: EventOne())
.triggers(EventOne.self)
.triggers(EventTwo.self)
.handleEvent(EventOne.self) {
print("Handled 1")
}
.handleEvent(EventTwo.self) {
print("Handled 2")
}
}
}

Expand Down
16 changes: 8 additions & 8 deletions Sources/HierarchyResponder/Internal/EnvironmentValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ struct RequiresExplicitRespondersKey: EnvironmentKey {
static var defaultValue = true
}

struct RegisteredEventsKey: EnvironmentKey {
struct HandledEventsKey: EnvironmentKey {
static var defaultValue: [any Event.Type] = []
}

struct RegisteredErrorsKey: EnvironmentKey {
struct HandledErrorsKey: EnvironmentKey {
static var defaultValue: [any Error.Type] = []
}

Expand All @@ -41,13 +41,13 @@ extension EnvironmentValues {
set { self[RequiresExplicitRespondersKey.self] = newValue }
}

var registeredEvents: [any Event.Type] {
get { self[RegisteredEventsKey.self] }
set { self[RegisteredEventsKey.self] = newValue }
var handledEvents: [any Event.Type] {
get { self[HandledEventsKey.self] }
set { self[HandledEventsKey.self] = newValue }
}

var registeredErrors: [any Error.Type] {
get { self[RegisteredErrorsKey.self] }
set { self[RegisteredErrorsKey.self] = newValue }
var handledErrors: [any Error.Type] {
get { self[HandledErrorsKey.self] }
set { self[HandledErrorsKey.self] = newValue }
}
}
12 changes: 10 additions & 2 deletions Sources/HierarchyResponder/Internal/ErrorSafetyModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ struct ErrorSafetyModifier: ViewModifier {
@Environment(\.requiresExplicitResponders) var requiresExplicitResponders
@Environment(\.responderSafetyLevel) var safetyLevel

@Environment(\.registeredErrors) var registeredErrors
@Environment(\.handledErrors) var receivedErrors

let errors: [any Error.Type]
let location: String

@State var declaredErrors: [any Error.Type] = []

var allDeclaredErrors: [any Error.Type] {
errors + declaredErrors
}

func body(content: Content) -> some View {
content
.receiveError { error in
Expand All @@ -25,10 +31,12 @@ struct ErrorSafetyModifier: ViewModifier {
}
return .notHandled
}
.onPreferenceChange(DeclaredErrorsKey.self) { value in declaredErrors = value.errors }
.preference(key: DeclaredErrorsKey.self, value: .init(errors: allDeclaredErrors))
.onAppear {
guard requiresExplicitResponders else { return }
let unregistered = errors.filter { event in
!registeredErrors.contains { $0 == event }
!receivedErrors.contains { $0 == event }
}
if unregistered.isEmpty { return }
let errorsString = unregistered.map { String(describing: $0) }.joined(separator: ", ")
Expand Down
13 changes: 10 additions & 3 deletions Sources/HierarchyResponder/Internal/EventSafetyModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@ struct EventSafetyModifier: ViewModifier {
@Environment(\.requiresExplicitResponders) var requiresExplicitResponders
@Environment(\.responderSafetyLevel) var safetyLevel

@Environment(\.registeredEvents) var registeredEvents
@Environment(\.handledEvents) var receivedEvents

let events: [any Event.Type]
let location: String

@State var declaredEvents: [any Event.Type] = []
var allDeclaredEvents: [any Event.Type] {
events + declaredEvents
}

func body(content: Content) -> some View {
content
.receiveEvent { event in
let found = events.contains { type(of: event) == $0 }
let found = (events + declaredEvents).contains { type(of: event) == $0 }
if found { return .notHandled }
switch safetyLevel {
case .disabled: break
Expand All @@ -25,10 +30,12 @@ struct EventSafetyModifier: ViewModifier {
}
return .notHandled
}
.onPreferenceChange(DeclaredEventsKey.self) { value in declaredEvents = value.events }
.preference(key: DeclaredEventsKey.self, value: .init(events: allDeclaredEvents))
.onAppear {
guard requiresExplicitResponders else { return }
let unregistered = events.filter { event in
!registeredEvents.contains { $0 == event }
!receivedEvents.contains { $0 == event }
}
if unregistered.isEmpty { return }
let eventsString = unregistered.map { String(describing: $0) }.joined(separator: ", ")
Expand Down
41 changes: 41 additions & 0 deletions Sources/HierarchyResponder/Internal/Preferences.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Created by Emilio Peláez on 18/10/23.
//

import SwiftUI

struct DeclaredEventsKey: PreferenceKey {
struct Container {
var events: [any Event.Type]
}

static var defaultValue: Container = .init(events: [])

static func reduce(value: inout Container, nextValue: () -> Container) {
value.events.append(contentsOf: nextValue().events)
}
}

struct DeclaredErrorsKey: PreferenceKey {
struct Container {
var errors: [any Error.Type]
}

static var defaultValue: Container = .init(errors: [])

static func reduce(value: inout Container, nextValue: () -> Container) {
value.errors.append(contentsOf: nextValue().errors)
}
}

extension DeclaredEventsKey.Container: Equatable {
static func == (lhs: DeclaredEventsKey.Container, rhs: DeclaredEventsKey.Container) -> Bool {
lhs.events.map { String(describing: $0) } == rhs.events.map { String(describing: $0) }
}
}

extension DeclaredErrorsKey.Container: Equatable {
static func == (lhs: DeclaredErrorsKey.Container, rhs: DeclaredErrorsKey.Container) -> Bool {
lhs.errors.map { String(describing: $0) } == rhs.errors.map { String(describing: $0) }
}
}
4 changes: 2 additions & 2 deletions Sources/HierarchyResponder/Safety/Safety.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ public extension View {
}

func registerHandler(for event: any Event.Type) -> some View {
transformEnvironment(\.registeredEvents) { $0.append(event) }
transformEnvironment(\.handledEvents) { $0.append(event) }
}

func registerHandler(for error: any Error.Type) -> some View {
transformEnvironment(\.registeredErrors) { $0.append(error) }
transformEnvironment(\.handledErrors) { $0.append(error) }
}
}

0 comments on commit 656749e

Please sign in to comment.