Detect how close the iPhone is to the Mac using BLE? - ios

I'm trying to detect if the iPhone is placed close enough to the Mac using BLE signal strength. I am using the following code to detect if the iPhone is in range(and it works), but I don't know how to detect if the iPhone is 'close enough'. Like -30db for instance. Does anyone know how I can achieve that? Any comments/discussions are welcome :)
import SwiftUI
import AVFoundation
import IOBluetooth
import CoreBluetooth
import Foundation
class BluetoothSearchDelegate : NSObject, IOBluetoothDeviceInquiryDelegate {
func deviceInquiryDeviceFound(_ sender: IOBluetoothDeviceInquiry, device: IOBluetoothDevice) {
print("Found Device \(device.name ?? "nil")")
}
func deviceInquiryStarted(_ sender: IOBluetoothDeviceInquiry) {
print("started")
}
func deviceInquiryComplete(_ sender: IOBluetoothDeviceInquiry, error: IOReturn, aborted: Bool) {
print("completed")
}
}
struct ContentView: View {
let delegate = BluetoothSearchDelegate()
let inquiry: IOBluetoothDeviceInquiry
init() {
self.inquiry = IOBluetoothDeviceInquiry(delegate: delegate)
}
var body: some View {
Button("Bluetooth") {
inquiry.start()
}
}
}

Related

Creating a SwiftUI UIViewControllerRepresentable for a ViewController that doesn't have a ViewController Delegate

I am trying to build a SwiftUI tvOS app.
As you can see here, I am trying to create a SwiftUI View using a UIViewControllerRepresentable, specifically for the DDDevicePickerViewController.
However, I noticed that there is no DDDevicePickerViewControllerDelegate while I was trying to implement it, which is needed according to Paul Hudson's tutorial. How can I use the DevicePickerView in SwiftUI?
I tried to use this code to create it, so when I use it, I just get a black screen with no errors in the logs:
import Foundation
import SwiftUI
import DeviceDiscoveryUI
public struct DDevicePickerView: UIViewControllerRepresentable {
let viewController: DDDevicePickerViewController
public init() {
// Create the view controller for the device picker.
let devicePicker = DDDevicePickerViewController(browseDescriptor: .applicationService(name: "TicTacToe"),
parameters: applicationServiceParameters())
self.viewController = devicePicker!
}
public func makeUIViewController(context: Context) -> DDDevicePickerViewController {
let gkVC = viewController
return gkVC
}
public func updateUIViewController(_ uiViewController: DDDevicePickerViewController, context: Context) {
return
}
}
SwiftUI already has a wrapper DevicePicker
But if you want to wrap it yourself, start with something like this and then you just have to figure out how to get the async endpoint result. It is quite unusual to have view controllers be async like this.
import Foundation
import SwiftUI
import DeviceDiscoveryUI
public struct DevicePicker: UIViewControllerRepresentable {
#Binding var isPresented: Bool
public func makeUIViewController(context: Context) -> UIViewController {
UIViewController()
}
public func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
if isPresented {
if uiViewController.presentedViewController != nil {
return
}
let = picker DDDevicePickerViewController(browseDescriptor: .applicationService(name: "TicTacToe"), parameters: applicationServiceParameters())
uiViewController.present(picker, animated: !context.transaction.disablesAnimations)
}
else {
uiViewController.presentedViewController?.dismiss()
}
}
}

How do I use TextField with suggestions for WatchOS SwiftUI

Currently there is no suggestion input for TextField.
I found an extension to use TextField with suggestions on WatchOS but this one is not working on a view which navigated with NavigationLink
Here is the wrapper code;
import Foundation
import SwiftUI
extension View {
typealias StringCompletion = (String) -> Void
func presentInputController(withSuggestions suggestions: [String], completion: #escaping StringCompletion) {
WKExtension.shared()
.visibleInterfaceController?
.presentTextInputController(withSuggestions: suggestions,
allowedInputMode: .allowEmoji) { result in
guard let result = result as? [String], let firstElement = result.first else {
completion("")
return
}
completion(firstElement)
}
}
}
I'm looking forward to your suggestions too :))

Error creating the CFMessagePort needed to communicate with PPT

I try to present a CNContactPickerViewController inside a SwiftUI application using the UIViewControllerRepresentable protocol. As I already read, there seems to be a known issue for this not working, but I got it working quite ok using the workaround described here.
However, whenever the CNContactPickerViewController gets presented or dismissed resp., I get the following error in my output log:
[PPT] Error creating the CFMessagePort needed to communicate with PPT.
I tried to find explanations on this, but there seems to be no answer anywhere on the internet. Does someone know where this error comes from and what PPT is? Could this error have something to do with the CNContactPickerViewController not working properly with SwiftUI?
I noticed the error for the first time in the iOS 14 beta together with the Xcode 12 beta, and it is still present in iOS 14.2 with Xcode 12.2.
I don't know if the error appears on iOS 13 as well.
I already issued a feedback report about this.
I wrote a workaround using a hosting UINavigationController and here is my code:
import SwiftUI
import ContactsUI
struct ContactPickerView: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
func makeUIViewController(context: Context) -> UINavigationController {
let navController = UINavigationController()
let controller = CNContactPickerViewController()
controller.delegate = context.coordinator
navController.present(controller, animated: false, completion: nil)
return navController
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
print("Updating the contacts controller!")
}
// MARK: ViewController Representable delegate methods
func makeCoordinator() -> ContactsCoordinator {
return ContactsCoordinator(self)
}
class ContactsCoordinator : NSObject, UINavigationControllerDelegate, CNContactPickerDelegate {
let parent: ContactPickerView
public init(_ parent: ContactPickerView) {
self.parent = parent
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
print("Contact picked cancelled!")
parent.presentationMode.wrappedValue.dismiss()
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
print("Selected a contact")
parent.presentationMode.wrappedValue.dismiss()
}
}
}
And I use it like:
Button("Select a contact") {
openSelectContact.toggle()
}
.sheet(isPresented: $openSelectContact, onDismiss: nil) {
ContactPickerView()
}

CNContactViewControllerDelegate not called when using the Coordinator pattern in SwiftUI

I've been grinding on this issue for quite a few days now and it seems like SwiftUI's relative "newness" doesn't seem to help me with this.
My gut feeling is that I'm somehow using CNContactViewControllerDelegate wrong but both Apple's documentation as well as other questions on SO make it seem like it should work. Maybe, it is also caused by .sheet's handling, but I wasn't able to isolate the issue as well. Not wrapping NewContactView inside NavigationView also made no difference and removed the default navigation bar (as expected).
I'm showing the CNContactViewController with init(forNewContact:) to give the app's users an ability to add new contacts. Persisting and everything works fine, however, none of the delegate's functions seem to get called.
Neither dismissing the modal with the "swipe to dismiss" gesture introduced in iOS 13 calls the delegate function, nor using the navigation buttons provided by CNContactViewController.
import Foundation
import SwiftUI
import ContactsUI
struct NewContactView: UIViewControllerRepresentable {
class Coordinator: NSObject, CNContactViewControllerDelegate, UINavigationControllerDelegate {
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
if let c = contact {
self.parent.contact = c
}
viewController.dismiss(animated: true)
}
func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool {
return true
}
var parent: NewContactView
init(_ parent: NewContactView) {
self.parent = parent
}
}
#Binding var contact: CNContact
init(contact: Binding<CNContact>) {
self._contact = contact
}
typealias UIViewControllerType = CNContactViewController
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<NewContactView>) -> NewContactView.UIViewControllerType {
let vc = CNContactViewController(forNewContact: CNContact())
vc.delegate = makeCoordinator()
return vc
}
func updateUIViewController(_ uiViewController: NewContactView.UIViewControllerType, context: UIViewControllerRepresentableContext<NewContactView>) {
}
}
The view's code for showing the controller looks like this:
.sheet(isPresented: self.$viewModel.showNewContact, onDismiss: { self.viewModel.fetchContacts() }) {
NavigationView() {
NewContactView(contact: self.$viewModel.newContact)
}
}
Thank you for any pointers! Rubberducking sadly didn't help...
SwiftUI creates coordinator by itself and provides it to representable in context, so just use
func makeUIViewController(context: UIViewControllerRepresentableContext<NewContactView>) -> NewContactView.UIViewControllerType {
let vc = CNContactViewController(forNewContact: CNContact())
vc.delegate = context.coordinator // << here !!
return vc
}

Why am I unable to get accelerometer values inside a Cocoa framework

I am trying to create a framework for a partner company that accesses a devices accelerometer. I am coming from an Android background and iOS development is still very new to me, so I might miss something obvious. If that is important I am using Xcode 9, Swift 4 and an iPhone 6 Plus for tests.
The first thing I did was try out the basic CoreMotion tutorials to create very basic app, that just prints current accelerometer values to the console. It looks like:
import UIKit
import CoreMotion
class ViewController: UIViewController {
private let motionManager = CMMotionManager()
override func viewDidAppear(_ animated: Bool) {
motionManager.startAccelerometerUpdates(to:OperationQueue.current!) { (data, error) in
print("Getting Accelerometer Update")
}
}
}
This works perfectly fine.
If I take the same code and move it to a cocoa framework project it simply does nothing. The closure for startAccelerometerUpdates is never called. For this case the changed ViewController looks like:
ViewController.swift inside the app project
import UIKit
import DataCapturing
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
let dc = DataCapturingService()
dc.start()
}
}
The class in my framework looks like:
DataCapturingService.swift inside the DataCapturing framework
import Foundation
import CoreMotion
public class DataCapturingService {
private let motionManager = CMMotionManager()
public init() {
}
public func start() {
motionManager.startAccelerometerUpdates(to: OperationQueue.current!) { data, error in
print("Getting Accelerometer Update")
}
}
}
Are there any restrictions on accessing sensor hardware from inside a framework? Do I need to provide any permissions somehow? The documentation unfortunately does not tell anything about this (or I may be to dumb to find it).
EDIT: Interestingly if I inject the CMMotionManager from the app projects ViewController everything works fine btw. For this case the changed ViewController looks like:
ViewController.swift inside the app project
import UIKit
import CoreMotion
import DataCapturing
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
let motionManager = CMMotionManager()
let dc = DataCapturingService(motionManager)
dc.start()
}
}
The class in my framework looks like:
DataCapturingService.swift inside the DataCapturing framework
import Foundation
import CoreMotion
public class DataCapturingService {
private let motionManager
public init(_ manager: CMMotionManager) {
self.motionManager = manager
}
public func start() {
motionManager.startAccelerometerUpdates(to: OperationQueue.current!) { data, error in
print("Getting Accelerometer Update")
}
}
}

Resources