My app reports and records location, altitude, rotation and accelerometer data (DeviceMotion) while in the background. This works fine on ios 10.3.3. On IOS 11, I no longer have access motion data while the device is locked. Altitude data and location data is still streaming to the console, though.
Has something changed in IOS 11 that prevents me from accessing motion data or am I doing trying to access it in a way that Apple now blocks like OperationQueue.main
Here is how I'm starting motion updates. If the phone is unlocked, all works fine. If I locking the phone, no more updates.:
let motionManager = self.motionManager
if motionManager.isDeviceMotionAvailable {
motionUpdateInterval = 0.15
motionManager.deviceMotionUpdateInterval = motionUpdateInterval
motionManager.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: OperationQueue.main) {deviceMotion, error in
guard let deviceMotion = deviceMotion else { return }
I can't find anything about Motion background modes changing but it seems there must be a way otherwise RunKeeper, Strava will break. Can someone help me get this working again before IOS11 launch?
Thanks!
Also came across this problem.
Our solution was to ensure we have another background mode enabled and running (in our case location updates + audio) and restart core motion updates when switching background/foreground.
Code sample:
import UIKit
import CoreMotion
final class MotionDetector {
private let motionManager = CMMotionManager()
private let opQueue: OperationQueue = {
let o = OperationQueue()
o.name = "core-motion-updates"
return o
}()
private var shouldRestartMotionUpdates = false
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(appDidEnterBackground),
name: .UIApplicationDidEnterBackground,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(appDidBecomeActive),
name: .UIApplicationDidBecomeActive,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self,
name: .UIApplicationDidEnterBackground,
object: nil)
NotificationCenter.default.removeObserver(self,
name: .UIApplicationDidBecomeActive,
object: nil)
}
func start() {
self.shouldRestartMotionUpdates = true
self.restartMotionUpdates()
}
func stop() {
self.shouldRestartMotionUpdates = false
self.motionManager.stopDeviceMotionUpdates()
}
#objc private func appDidEnterBackground() {
self.restartMotionUpdates()
}
#objc private func appDidBecomeActive() {
self.restartMotionUpdates()
}
private func restartMotionUpdates() {
guard self.shouldRestartMotionUpdates else { return }
self.motionManager.stopDeviceMotionUpdates()
self.motionManager.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: self.opQueue) { deviceMotion, error in
guard let deviceMotion = deviceMotion else { return }
print(deviceMotion)
}
}
}
The official 11.1 release fixed the issue and I've heard from iPhone 8 users that the original implementation is working for them.
The 11.2 beta has not broken anything.
Related
I have a simple UIKit application that has a UITextView in a UICollectionViewCell. The app is designed for iOS/iPadOS and works just fine on those platforms. However, when run on Mac (Designed for iPad) as soon as I start scrolling the collectionview, the cpu usage spikes to ~85% and stays there indefinitely. The only way to lower the cpu is to click outside of the application window, but once it comes to the foreground again, the cpu usage jumps right back up. I've tried running on Mac in Catalyst mode too, but the same problem occurs with slightly less cpu usage (~45%).
Additionally the debugger constantly spits out [API] cannot add handler to 3 from 3 - dropping while scrolling.
Does anyone have an explanation or solutions for this?
I’m using Xcode Version 14.1 (14B47b) on macOS Ventura 13.0 (22A380).
class ViewController: UIViewController {
var dataSource: UICollectionViewDiffableDataSource<Section, String>! = nil
var collectionView: UICollectionView! = nil
var items = Array(0...100).map{"Item \($0)"}
enum Section: String {
case main
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
configureCollectionView()
configureDataSource()
applyInitialSnapshot()
}
private func createLayout() -> UICollectionViewLayout {
return UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitems: [item])
return NSCollectionLayoutSection(group: group)
}
}
private func configureCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.backgroundColor = .systemBackground
view.addSubview(collectionView)
}
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<TestCell, String> { (cell, indexPath, item) in
cell.configure(title: item, row: indexPath.item)
}
dataSource = UICollectionViewDiffableDataSource<Section, String>(collectionView: collectionView) {
(collectionView, indexPath, identifier) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
}
private func applyInitialSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: false)
}
}
class TestCell: UICollectionViewCell {
private let annotationsTextView = UITextView()
override init(frame: CGRect) {
super.init(frame: frame)
addViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(title: String, row: Int) {
annotationsTextView.attributedText = .init(string: "Row: \(row) Item: \(title)", attributes: [.font: UIFont.preferredFont(forTextStyle: .title1)])
}
private func addViews() {
annotationsTextView.isScrollEnabled = false
annotationsTextView.isEditable = false
annotationsTextView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(annotationsTextView)
NSLayoutConstraint.activate([
annotationsTextView.topAnchor.constraint(equalTo: contentView.topAnchor),
annotationsTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
annotationsTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
annotationsTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
}
}
I am still on 13.0.1 and had the same problem (tons of [API] cannot add handler to 3 from 3 - dropping). I narrowed it down to a call to UITableView.scrollToNearestSelectedRow with animated = true.
Setting animated to false stops the logging. I guess I’ll have to check out 13.1
I'm experiencing this exact behavior in Ventura whenever there is a UITextView anywhere in the view hierarchy with either isSelectable or isEditable set to true, but only while the UITextView is NOT first responder. This occurs whether the UITextView is visible or hidden.
The excess CPU usage can be prevented by any of the following:
Remove the UITextView from the view hierarchy.
Set isSelectable AND isEditable to false.
Make the UITextView first responder.
I'm still investigating and will update here if I find more. We should all probably report the issue to Apple as I imagine they will need to fix this at the system level.
2022-12-01: Update
This issue appears to have been resolved as of macOS 13.1 beta 4 (22C5059b). Hallelujah!
as per the documentation, it should be pretty straightforward. example for a List: https://developer.apple.com/documentation/swiftui/list/ondrop(of:istargeted:perform:)-75hvy#
the UTType should be the parameter restricting what a SwiftUI object can receive. in my case i want to accept only Apps. the UTType is .applicationBundle: https://developer.apple.com/documentation/uniformtypeidentifiers/uttype/3551459-applicationbundle
but it doesn't work. the SwiftUI object never changes status and never accepts the drop. the closure is never run. whether on Lists, H/VStacks, Buttons, whatever. the pdf type don't seem to work either, as well as many others. the only type that i'm able to use if fileURL, which is mainly like no restriction.
i'm not sure if i'm doing something wrong or if SwiftUI is half working for the mac.
here's the code:
List(appsToIgnore, id: \.self, selection: $selection) {
Text($0)
}
.onDrop(of: [.applicationBundle, .application], isTargeted: isTargeted) { providers in
print("hehe")
return true
}
replacing or just adding .fileURL in the UTType array makes the drop work but without any type restriction.
i've also tried to use .onInsert on a ForEach instead (https://developer.apple.com/documentation/swiftui/foreach/oninsert(of:perform:)-2whxl#), and to go through a proper DropDelegate (https://developer.apple.com/documentation/swiftui/dropdelegate#) but keep getting the same results. it would seem the SwiftUI drop for macOS is not yet working, but i can't find any official information about this. in the docs it is written macOS 11.0+ so i would expect it to work?
any info appreciated! thanks.
You need to validate manually, using DropDelegate of what kind of file is dragged over.
Here is a simplified demo of possible approach. Tested with Xcode 13 / macOS 11.6
let delegate = MyDelegate()
...
List(appsToIgnore, id: \.self, selection: $selection) {
Text($0)
}
.onDrop(of: [.fileURL], delegate: delegate) // << accept file URLs
and verification part like
class MyDelegate: DropDelegate {
func validateDrop(info: DropInfo) -> Bool {
// find provider with file URL
guard info.hasItemsConforming(to: [.fileURL]) else { return false }
guard let provider = info.itemProviders(for: [.fileURL]).first else { return false }
var result = false
if provider.canLoadObject(ofClass: String.self) {
let group = DispatchGroup()
group.enter() // << make decoding sync
// decode URL from item provider
_ = provider.loadObject(ofClass: String.self) { value, _ in
defer { group.leave() }
guard let fileURL = value, let url = URL(string: fileURL) else { return }
// verify type of content by URL
let flag = try? url.resourceValues(forKeys: [.contentTypeKey]).contentType == .applicationBundle
result = flag ?? false
}
// wait a bit for verification result
_ = group.wait(timeout: .now() + 0.5)
}
return result
}
func performDrop(info: DropInfo) -> Bool {
// handling code is here
return true
}
}
I'm trying to get access to the gyroscope of the apple watch. From what I read it is available in watchos 3. Unfortunately I cannot get it to work. It keeps coming back with "Gyro not available" so motionManager.isGyroAvailable is always false. Here is my code. Any help would be appreciated.
import WatchKit
import Foundation
import CoreMotion
class InterfaceController: WKInterfaceController {
let motionManager = CMMotionManager()
override func awake(withContext context: Any?) {
super.awake(withContext: context)
motionManager.gyroUpdateInterval = 0.1
motionManager.accelerometerUpdateInterval = 0.1
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
if (motionManager.isGyroAvailable == true) {
motionManager.startGyroUpdates(to: OperationQueue.current!, withHandler: { (data, error) -> Void in
guard let data = data else { return }
let rotationX = data.rotationRate.x
let rotationY = data.rotationRate.y
let rotationZ = data.rotationRate.z
// do you want to want to do with the data
print(rotationX)
print(rotationY)
print(rotationZ)
})
} else {
print("Gyro not available")
}
From my experience (although I can't find it documented anywhere) raw gyroscope data isn't available on the watch, only the processed data. You can access the processed data using the CMMotionManager method:
startDeviceMotionUpdates(to queue: OperationQueue, withHandler handler: #escaping CMDeviceMotionHandler)
The CMDeviceMotion object in the handler has detailed rotation data, for instance the rotation rate, the documentation for this states that it's processed data from the gyroscope. There is also attitude data.
I am trying to implement window toggling (something I've done many times in Objective-C), but now in Swift. It seams that I am getting the use of NSWindowOcclusionState.Visible incorrectly, but I really cannot see my problem. Only the line w.makeKeyAndOrderFront(self) is called after the initial window creation.
Any suggestions?
var fileArchiveListWindow: NSWindow? = nil
#IBAction func tougleFileArchiveList(sender: NSMenuItem) {
if let w = fileArchiveListWindow {
if w.occlusionState == NSWindowOcclusionState.Visible {
w.orderOut(self)
}
else {
w.makeKeyAndOrderFront(self)
}
}
else {
let sb = NSStoryboard(name: "FileArchiveOverview",bundle: nil)
let controller: FileArchiveOverviewWindowController = sb?.instantiateControllerWithIdentifier("FileArchiveOverviewController") as FileArchiveOverviewWindowController
fileArchiveListWindow = controller.window
fileArchiveListWindow?.makeKeyAndOrderFront(self)
}
}
Old question, but I just run into the same problem. Checking the occlusionState is done a bit differently in Swift using the AND binary operator:
if (window.occlusionState & NSWindowOcclusionState.Visible != nil) {
// visible
}
else {
// not visible
}
In recent SDKs, the NSWindowOcclusionState bitmask is imported into Swift as an OptionSet. You can use window.occlusionState.contains(.visible) to check if a window is visible or not (fully occluded).
Example:
observerToken = NotificationCenter.default.addObserver(forName: NSWindow.didChangeOcclusionStateNotification, object: window, queue: nil) { note in
let window = note.object as! NSWindow
if window.occlusionState.contains(.visible) {
// window at least partially visible, resume power-hungry calculations
} else {
// window completely occluded, throttle down timers, CPU, etc.
}
}
I'm working on my first app for OSX 10.8 using Swift. I want to be able to have the state of the battery dictate the text in Today pulldown menu. That aspect of the code works, but I am very frustrated as my class never gets called. It is in a separate supporting script called 'DeviceMonitor.swift'. Thanks for the help!
Code:
import Foundation
import UIKit
class BatteryState {
var device: UIDevice
init() {
self.device = UIDevice.currentDevice()
println("Device Initialized")
}
func isPluggedIn(value: Bool) {
let sharedDefaults = NSUserDefaults(suiteName: "group.WidgetExtension")
let batteryState = self.device.batteryState
if (batteryState == UIDeviceBatteryState.Charging || batteryState == UIDeviceBatteryState.Full){
let isPluggedIn = true
println("Plugged In")
sharedDefaults?.setObject("Plugged In", forKey: "stringKey")
}
else {
let isPluggedIn = false
println("Not Plugged In")
sharedDefaults?.setObject("Not Plugged In", forKey: "stringKey")
}
sharedDefaults?.synchronize()
}
}
Unless you have done it somewhere else in your code, it looks like you haven't registered to receive UIDeviceBatteryLevelDidChangeNotification events. You should also ensure that the current UIDevice has batteryMonitoringEnabled as YES.