Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.2.0 #15

Merged
merged 13 commits into from
Jan 18, 2023
Merged

0.2.0 #15

merged 13 commits into from
Jan 18, 2023

Conversation

robb
Copy link
Contributor

@robb robb commented Dec 7, 2022

New in 0.2.0

Warning

Edges and angles for .boing, .glare, .move, and .wipe transitions as well as the .shine change effect have been changed to be consistent:

  • .boing has a new default direction moving from the top instead of the bottom.
  • Angle given to these transitions and effects are reversed from what they used to be meaning that a 90° angle now moves towards the bottom edge instead of the top edge.
  • Added sound change effect.
  • Added impact and selection haptic feedback types.
  • Added particleLayer(name:) view modifier.
  • .glare and .vanish now display with increased brightness.
  • Added delay(_:) modifier to change effects.

Haptics

In addition to notification haptics, you can now trigger also selection and impact haptics.

Picker("Color", selection: $color) {
    Text("Red").tag(.red)
    Text("Green").tag(.green)
    Text("Blue").tag(.blue)
}
.pickerStyle(.segmented)
.changeEffect(.feedbackHapticSelection, value: color)

Sound Effects

This version of Pow introduces Sound Effects.

Play sound effects using the .feedback(_:) change effect.

Button(status.buttonLabelText) {
    Task {
        do {
            status = .inProgress
            try await processPayment()
            status = .paid
        } catch {
            status = .failed
        }
    }
}
.changeEffect(.feedback(SoundEffect("sparkle")), value: status == .paid, isEnabled: status == .paid)
.changeEffect(.feedback(SoundEffect("notfound")), value: status == .failed, isEnabled: status == .failed)

The sounds are looked up in the main Bundle by default. Common audio formats like aiff, wav, caf, and m4a are found automatically but you can also use other formats by specifying the type and if supported by the OS.

A set of sounds can be found in the Pow Example repo and are free to use with any licensed copy of Pow.

Our thanks to @mergesort for the feature requests to add more haptic feedback types and sound change effects 🙇

Particle Layer

Particle effects such as AnyChangeEffect.spray can now render their particles in a different position in the view tree to avoid being clipped by their immediate ancestor.

For example, certain List styles may clip their rows. Use particleLayer(_:) to render particles on top of the entire List or even its enclosing NavigationStack.

NavigationStack {
    List(items) { item in
        HStack {
            Text(item.title)
            Image(systemName: "heart.fill")
                .changeEffect(.spray(layer: .named("root")) {
                    Image(systemName: "heart.fill")
                        .foregroundStyle(.red)
                }, value: item.likes)
        }
    }
}
.particleLayer(name: "root")

Brighter .glare & .vanish

.glare and .vanish transitions now display with increased brightness giving a bit more punch to the effect. And for .glare it makes the shine show even on white backgrounds.

Delay

You can now add a delay to change effects to change the timing of the change effects. This works well with the .shine effect for example where you might want the shine highlight to show some time after the button becomes enabled.

Button("Submit") { 
    // … 
}
.buttonStyle(.borderedProminent)
.disabled(name.isEmpty)
.changeEffect(.shine.delay(1), value: name.isEmpty, isEnabled: !name.isEmpty)

If you're interested in using Pow in your app, you can purchase a license on our site. ✨

Thank you for your support!

@mergesort
Copy link
Collaborator

I noticed there's a usability difference between code I have and the code that your variant of SoundEffect vends. Given these are sound effects I'm setting the AVAudioSession category to .mixWithOthers like this, to allow me to play sounds when the user has headphones in, or when there's music playing from another app.

try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
try AVAudioSession.sharedInstance().setActive(true)

This may not always be the right option so the current way the framework chooses to play a sound is likely a sensible default to provide, but it is something I'd like to have control over. If it helps the way I'm currently checking the audio source and the user's preference before setting the audio category like this, and then based on a preference deciding whether to play it (or in Pow parlance to set isEnabled).

var audioSourceIsHeadphones: Bool {
    AVAudioSession.sharedInstance()
        .currentRoute
        .outputs
        .map(\.portType)
        .contains(where: { $0 == .headphones || $0 == .bluetoothA2DP })
}

var audioSourceIsSpeakers: Bool {
    AVAudioSession.sharedInstance()
        .currentRoute
        .outputs
        .map(\.portType)
        .contains(where: { $0 == .builtInSpeaker })
}

var audioSourceIsOutputDevice: Bool {
    return self.audioSourceIsHeadphones || self.audioSourceIsSpeakers
}

@kasper-lahti
Copy link
Contributor

Thanks for testing this out.

Yeah I agree, this would be good have some control over. I think we can add an option to let you set the AVAudioSession to play these sounds with.

@mergesort
Copy link
Collaborator

Sounds great, thanks a lot! I also realized in my earlier message that I said the current version could be a sensible default, but after giving it some more thought I realized that this ChangeEffect is intended for sound effects rather than something intrusive like a song, so I think defaulting to .mixWithOthers may actually be the better default. Either way, as long as there's a way for me to choose I'll be happy, so thank you again for all the work, from a huge Pow fan. 😄

@kasper-lahti
Copy link
Contributor

Great to hear! And I also think defaulting to .mixWithOthers would be a better default, thanks for giving this some more thought, much appreciated.

@robb
Copy link
Contributor Author

robb commented Jan 18, 2023

Ok, after looking into this we decided on our approach for SoundEffect for 0.2.0

  • Generally, SoundEffect is meant for short, ambient sound effects, meaning they are not critical to the experience of the app and e.g. obey the silent switch on iPhone.1
  • As such, they mix with other audio on the system and they don't duck it.
  • Since they are ambient and triggered in response to changes, they will likely coincide with a visual indicator of some sort as well.
  • Making the audio and visual effects line up well requires low-latency playback.
  • Internally, we are currently using CHHapticEngine for low-latency playback. In its default configuration, CHHapticEngine doesn't play sounds over headphones but we believe this is a reasonable choice for SoundEffects if we cannot make guarantees regarding the latency of e.g. bluetooth headphones.
  • At the time of a change effect and inside the SwiftUI component that triggers it, reasoning about the global state of the AVAudioSession is tricky – another part of the app may be playing a video, podcast or require different semantics.
  • Therefore, we will follow the default CHHapticEngine behavior and mix them with other audio and not trigger SoundEffects over headphones.

Part of this is motivated by the internal use of CHHapticEngine to trigger sound effects but we believe that's the simplest compromise regardless of our use of the haptic engine.

This also gives us the most option to integrate with an AVAudioSession later, e.g. to allow ducking or playback via headphones. (AVAudioSessions singleton nature doesn't make this particularly appealing in a SwiftUI-world that leans heavily on Environemt-based dependency injection 🤷.)

@mergesort: let me know if this makes sense to you, we're happy to revisit this in the future if this compromise proves too simplistic. Also, do you happen to know if problems like these are why refreshing the main feed in the twitter app sometimes pauses audio playback? 😬

Footnotes

  1. This is in line with other Pow effects which you might consider deactivating, e.g. in reponse to the accessibilityReduceMotion environment flag.

@robb robb merged commit 0d063d4 into main Jan 18, 2023
@robb robb deleted the release/0.2.0 branch January 18, 2023 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature Request: More Haptic Options Feature Request: Sound ChangeEffect
4 participants