Adding custom notifications with Reality Composer - arkit

New to Reality Composer and ARKit, using iOS 16
Created a scene with RealityKit, with a sphere in it.
Added a custom notification to it, added it, works but it doesn't...
Need to tap hard to get this to fire, and won't fire reliably? What am I missing?
BoardSquare.loadBoardAsync(completion: { ( loading ) in
switch loading {
case .success(let boardBoard):
boardBoard.generateCollisionShapes(recursive: true)
anchor.addChild(boardBoard)
boardBoard.actions.customNotification.onAction = { entity in
print("custom action")
}
anchor.addChild((boardBoard.ball)!)
case .failure(let error):
print(error.localizedDescription)
}
})

Related

AVPlayerViewController and AVPictureInPictureController

I have PIP working using AVPlayer, AVPlayerViewController
By default, when entering back into the app/view from background with PIP active, I'd like it to return back into the view.
My issue is that when I setup the AVPictureInPictureController using my AVPlayerLayer, I can get back pip observations however, none of the pipController methods like stopPictureInPicture() or isPictureInPictureActive work or return false.
How can I get both AVPlayerViewController and AVPictureInPictureController to work together?
if AVPictureInPictureController.isPictureInPictureSupported() {
// Create a new controller, passing the reference to the AVPlayerLayer.
DispatchQueue.main.async {
videoController.pipController = AVPictureInPictureController(playerLayer: videoController.playerLayer)
videoController.pipController!.delegate = videoController
videoController.pipPossibleObservation = videoController.pipController!.observe(\AVPictureInPictureController.isPictureInPicturePossible,
options: [.initial, .new]) { _, change in
// Update the PiP button's enabled state.
print("Can pip")
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
print("Stop Pip")
print("\(videoController.pipController!.isPictureInPictureActive)") // Returns false
}
}
}
} else {
// PiP isn't supported by the current device. Disable the PiP button.
print("Can not pip")
}

Mapbox add interactivity to polygon in FillLayer

I have a FillLayer with polygons generated from a local geojson file. I want to add interactivity to these polygons but am not sure how to do it with the iOS SDK. I found an example that is able to do something similar to what I need to do but in the Mapbox-GL-JS environment:
https://docs.mapbox.com/mapbox-gl-js/example/polygon-popup-on-click/
You can add a gesture recognizer to the map in the iOS SDK like this
mapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.mapClickedFunction)))
but you can't seem to add a gesture recognizer to the layer or the polygons inside the layer. Any help on this would be much appreciated!
Use the tap point received from the gesture recognizer to query the map for rendered features at the given point within the layer specified.
#objc public func findFeatures(_ sender: UITapGestureRecognizer) {
let tapPoint = sender.location(in: mapView)
mapView.mapboxMap.queryRenderedFeatures(
with: tapPoint,
options: RenderedQueryOptions(layerIds: ["US-states"], filter: nil)) { [weak self] result in
switch result {
case .success(let queriedfeatures):
if let firstFeature = queriedfeatures.first?.feature.properties,
case let .string(stateName) = firstFeature["STATE_NAME"] {
self?.showAlert(with: "You selected \(stateName)")
}
case .failure(let error):
self?.showAlert(with: "An error occurred: \(error.localizedDescription)")
}
}
}
This is a chunk of code from MapBox examples. The logic is to put the gesture recognizer on the map view. When a tap gesture is received you get the location where user tapped as CGPoint coordinate inside the map view. Then you send it to MapboxMap in order to get a list o features that contain that point.
I recommend you to take a look at the official Mapbox repository that contains a lot of ways of doing things:
https://github.com/mapbox/mapbox-maps-ios/tree/main/Apps/Examples
Have a nice day!

How to create apple watchOS5 complication?

I've never worked in WatchOS5 and want to develop a horizontal complication (Modular large) for AppleWatch, like "Heart Rate". The idea is that I would display heart rate data in a different way. Right now I want to deploy the complication on development watch.
I have created a new project with a checkbox for "complication" added. I see that this added a complications controller with timeline configuration placeholders.
There is also an storyboard with a bunch of empty screens. I'm not sure as to how much effort I need to put into an apple watch app before I can deploy it. I see this Apple doc, but it does not describe how to layout my complication. Some section seem to have missing links.
Can I provide one style of complication only (large horizontal - modular large)
Do I need to provide any iPhone app content beyond managing the
complication logic, or can I get away without having a view controller?
Do I control the appearance of my complication by adding something to the assets folder (it has a bunch of graphic slots)?
Sorry for a complete beginner project, I have not seen a project focusing specifically on the horizontal complication for watch OS 5
You should be able to deploy it immediately, though it won't do anything. Have a look at the wwdc video explaining how to create a complication: video
You can't layout the complication yourself, you can chose from a set of templates that you fill with data. The screens you are seeing are for your watch app, not the complication.
You don't have to support all complication styles.
The complication logic is part of your WatchKit Extension, so technically you don't need anything in the iOS companion app, I'm not sure how much functionality you have to provide to get past the app review though.
Adding your graphics to the asset catalog won't do anything, you have to reference them when configuring the templates.
Here's an example by Apple of how to communicate with the apple watch app. You need to painstakingly read the readme about 25 times to get all the app group identifiers changed in that project.
Your main phone app assets are not visible to the watch app
Your watch storyboard assets go in WatchKit target
Your programmatically accessed assets go into the watch extension target
Original answers:
Can I provide one style of complication only (large horizontal -
modular large) - YES
Do I need to provide any iPhone app content beyond
managing the complication logic, or can I get away without having a
view controller? YES - watch apps have computation limits imposed on them
Do I control the appearance of my complication by
adding something to the assets folder (it has a bunch of graphic
slots)? See below - it's both assets folder and placeholders
Modify the example above to create a placeholder image displayed on the watch (when you are selecting a complication while modifying the screen layout)
func getPlaceholderTemplate(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTemplate?) -> Void) {
// Pass the template to ClockKit.
if complication.family == .graphicRectangular {
// Display a random number string on the body.
let template = CLKComplicationTemplateGraphicRectangularLargeImage()
template.textProvider = CLKSimpleTextProvider(text: "---")
let image = UIImage(named: "imageFromWatchExtensionAssets") ?? UIImage()
template.imageProvider = CLKFullColorImageProvider(fullColorImage: image)
// Pass the entry to ClockKit.
handler(template)
}else {
handler(nil);
return
}
}
sending small packets to the watch (will not send images!)
func updateHeartRate(with sample: HKQuantitySample){
let context: [String: Any] = ["title": "String from phone"]
do {
try WCSession.default.updateApplicationContext(context)
} catch {
print("Failed to transmit app context")
}
}
Transferring images and files:
func uploadImage(_ image: UIImage, name: String, title: String = "") {
let data: Data? = UIImagePNGRepresentation(image)
do {
let fileManager = FileManager.default
let documentDirectory = try fileManager.url(for: .cachesDirectory,
in: .userDomainMask,
appropriateFor:nil,
create:true)
let fileURL = try FileManager.fileURL("\(name).png")
if fileManager.fileExists(atPath: fileURL.path) {
try fileManager.removeItem(at: fileURL)
try data?.write(to: fileURL, options: Data.WritingOptions.atomic)
} else {
try data?.write(to: fileURL, options: Data.WritingOptions.atomic)
}
if WCSession.default.activationState != .activated {
print("session not activated")
}
fileTransfer = WCSession.default.transferFile(fileURL, metadata: ["name":name, "title": title])
}
catch {
print(error)
}
print("Completed transfer \(name)")
}

Previous watchOS background budget overload preventing current background app refreshes

I'm developing a watchOS app that executes regularly in the background using background refresh tasks. A while ago I put in a call to update the watch's complication at each background refresh. This quickly burned through all of the complication updates and, I believe, the background budget for the app. I assumed it would reset at the beginning of the next day, but this doesn't seem to be the case. Since then, even though I've removed the complication refresh code altogether the app does not consistently execute background refreshes. I've deleted the app and reinstalled it and waited about a week but it only executes these refreshes regularly when run through Xcode (on the device), not when run independently. Any ideas?
Update:
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for backgroundTask in backgroundTasks {
switch backgroundTask {
case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
connectivityTask.setTaskCompleted()
case let refreshTask as WKApplicationRefreshBackgroundTask:
MainInterfaceController.scheduleTask()
refreshTask.setTaskCompleted()
case let snapshotTask as WKSnapshotRefreshBackgroundTask:
snapshotTask.setTaskCompleted()
default:
backgroundTask.setTaskCompleted()
}
}
DataProcessor.sharedInstance.dataProcessing()
public static func scheduleTask() {
let fireDate = Date(timeIntervalSinceNow: 10.0)
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: fireDate, userInfo: nil) { (error) in
if (error == nil) {
print("Background task scheduled")
}
}
}
And a task is scheduled when the application deactivates, kicking off the chain. This code was all working previously, which is the confusing part.

Activity indicator while creating csv file

I am trying to show an activity indicator while creating a csv file, but it does not show. I am guessing I should use dispatch_async somehow, but I cant figure out how to do this in swift 3.
var activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
override func viewDidLoad() {
super.viewDidLoad()
// activity indicator
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 100 ,y: 200,width: 50,height: 50)) as UIActivityIndicatorView
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
activityIndicator.center = self.view.center
self.view.addSubview(activityIndicator)
}
func writeToCsv() {
self.activityIndicator.startAnimating() // start the animation
let fileName = "events.csv"
let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
var csvText = self.name! + "\n"
csvText += "Date,Start time,End time\n"
// create rest of comma-separated string
for event in self.events! {
let newLine = "\(event.date),\(event.startTime),\(event.endTime)\n"
csvText.append(newLine)
}
// write to csv
do {
try csvText.write(to: path!, atomically: true, encoding: String.Encoding.utf8)
} catch {
print("Failed to create file")
print(error)
}
// create and present view controller with send options
let vc = UIActivityViewController(activityItems: [path as Any], applicationActivities: [])
self.present(vc, animated: true, completion: nil)
self.activityIndicator.stopAnimating() // stop the animation
}
Err, alright bit hard to answer this without a bit more context about your view setup. First of all, make sure your activity indicator is visible without calling the writeCsv method, so you know your view hierarchy is correct. ( I.E. It could be that it is hidden behind some other subview )
Next, in Swift3 Dispatch has been changed to a newer API. I'm not sure whether on OSX they use the raw libdispatch Swift wrapper, but in any case you access it like this:
Background default queue:
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { /* code */ }
Main thread:
DispatchQueue.main.async { /* Mainthread code ( UIKit stuff ) */ }
Your own custom queue for CSV generation blocks:
let queue = DispatchQueue(label: "csvgenerator.queue")
queue.async { /* code */ }
Now for your animating / stopAnimation, make sure you call your UIKit related code from the mainthread to prevent weird glitechs and or crashes
Namely:
DispatchQueue.main.async {
self.activityIndicator?.startAnimating()
}
Another good idea might be to use NSOperationQueue instead. It internally uses GCD I believe, but it does integrate very well into iOS and might make some of the dispatching a lot easier to implement. I myself always use GCD instead, but I have never really had long queeu's of work that needed to be done. One of the advantages of NSOperationQueue is that it is a lot more user friendly in terms of cancelling dispatch blocks.
An interesting session video about NSOperationQueue in the WWDC app written by Dave Delong: WWDC Videos 2015
A small minor changes I'd make to your writeCSV method:
guard let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName) else {
// Should throw an error here, or whatever is handy for your app
return
}
Try to avoid forced unwrapping at all stages where possible.
In methods that have this, you can for instance add "throws" to the end of the function definition so you can use try without the do and catch block, while also being able to throw errors in your guard statement so whatever calls writeCsv can catch the error and more easily display it to the user.