Reputation: 125
I found this code, and adapted it to my own app. It seems to work fine for my use. The issue I am experiencing and not sure how to resolve is that when I switch views or open a sheets over the PostView
. It seems like the motion updates continue even when they're not needed, causing the app to lag and become very sluggish and choppy as it seems to render the effect nonstop.
Code for motion effect:
import SwiftUI
import CoreMotion
class MotionManager: ObservableObject {
@Published var pitch: Double = 0.0
@Published var roll: Double = 0.0
private var manager: CMMotionManager
init() {
self.manager = CMMotionManager()
self.manager.deviceMotionUpdateInterval = 1/90
self.manager.startDeviceMotionUpdates(to: .main) { [weak self] (motionData, error) in
guard error == nil else {
print(error!)
return
}
if let motionData = motionData {
DispatchQueue.main.async {
let newPitch = motionData.attitude.pitch
let newRoll = motionData.attitude.roll
// Use nil-coalescing operator to provide a default value (e.g., 0.0) if nil
if abs(newPitch - (self?.pitch ?? 0.0)) > 0.01 || abs(newRoll - (self?.roll ?? 0.0)) > 0.01 {
self?.pitch = newPitch
self?.roll = newRoll
}
}
}
}
}
}
struct ParallaxMotionModifier: ViewModifier {
@ObservedObject var manager: MotionManager
var magnitude: Double
func body(content: Content) -> some View {
content
.offset(x: CGFloat(manager.roll * magnitude), y: CGFloat(manager.pitch * magnitude))
}
}
Here is my PostView
code snippet that renders random image dynamically inside a frame:
struct PostView: View {
enum LayoutType {
case singleImage, listView
}
var post: SomeTest
var layoutType: LayoutType
@StateObject private var motionManager = MotionManager()
var body: some View {
switch layoutType {
case .singleImage:
VStack {
ZStack(alignment: .topLeading) {
// Apply ParallaxMotionModifier to displayImageView
displayImageView(for: post)
.modifier(ParallaxMotionModifier(manager: motionManager, magnitude: 15))
.drawingGroup()
.frame(width: 370, height: 570)
.scaleEffect(1.1)
.cornerRadius(20)
.clipped()
.frame(width: 350, height: 550)
.cornerRadius(20)
.shadow(color: Color.black, radius: 6, x: 0, y: 0)
}
}
}
}
The issue happens when I switch from singleImage
(where the parallax effect is active) to listView
, or when I open a sheet for example over the singleImage
view. The scrolling in the list view becomes extremely laggy, and I suspect the motion effect from the single image view is still active and updating in the background.
Upvotes: 0
Views: 85
Reputation: 715
There are several issues with your implementation:
CMMotionManager
instance for your entire app as described in Apple's documentation:Create only one CMMotionManager object for your app. Multiple instances of this class can affect the rate at which the system receives data from the accelerometer and gyroscope.
I.e. you should avoid creating a CMMotionManager
instance per view, especially if you're doing it in a list of views.
main
operation queue to process the motion updates is not recommended by Apple:An operation queue provided by the caller. Because the processed events might arrive at a high rate, using the main operation queue is not recommended.
You must call stopDeviceMotionUpdates
when your views no longer need updates. This is also described in the official documentation.
The update rate of 1/90 is too high, may cost too many CPU resources and may negatively impact your UI since you're putting unnecessary workload on the main thread. You should consider reducing it to 1/30.
For the moment, I recommend a solution based on UIInterpolatingMotionEffect
until Apple provides a SwiftUI native solution.
This avoids many of the problems described. You can find an implementation in the Swift package I have just published.
Upvotes: 0