Assume I have an iPhone connected to a wifi network with 3+ access points.
I'd like to collect all possible fields around wifi access strength/signal/etc from EACH access point and use that to triangulate, even while in background.
while true {
...
for access_point in access_points {
...
signal_strength = ...
}
}
I've been reading previous SO answers and other posts, and seems like it wasn't allowed on iOS without a jailbreak for a while, but is now availiable again.
Anyone can show a code snippet of how I'd go about doing this? All new to iOS development..
It's been quite a while since I worked with this, so I did a quick check again and now I am fairly certain you misunderstood something you've read. As far as I can tell, Apple did not suddenly revert their previous decision to restrict the public frameworks to scan for access points, i.e. specific MAC addresses and their signal strength.
You can query the specific rssi (signal strength) for a network (i.e. for an ssid), but not for individual MAC addresses. Before iOS 5 you could do that using private APIs, then you could do it with private APIs on a jailbroken device and that's pretty much it.
I don't have the code of my own, old stuff at hand (I used to do this for indoor location tracking before we switched to use iBeacons), so I can't provide you with a sample snippet myself. My code is dated and no longer functioning anyways, but you might find something here.
I would be really interested in the sources you mention that claim iOS 10 now allows this again. Apple closed this for privacy considerations (officially at least, and although this might be true in part it also means developers dealing with location-tracking now need to rely fully on Apple's framework for that only), so I highly doubt they went back on it.
Also, note that this is for sure not something trivial, especially if you're new to iOS development. I haven't even tackled the background idea, you can safely forget about that, because no matter what you do, you will not have a scanner that runs continuously in the background. That's against a very core principle of iOS programming.
I've answered how to ping ALL wifi networks in this question;
func getInterfaces() -> Bool {
guard let unwrappedCFArrayInterfaces = CNCopySupportedInterfaces() else {
print("this must be a simulator, no interfaces found")
return false
}
guard let swiftInterfaces = (unwrappedCFArrayInterfaces as NSArray) as? [String] else {
print("System error: did not come back as array of Strings")
return false
}
for interface in swiftInterfaces {
print("Looking up SSID info for \(interface)") // en0
guard let unwrappedCFDictionaryForInterface = CNCopyCurrentNetworkInfo(interface) else {
print("System error: \(interface) has no information")
return false
}
guard let SSIDDict = (unwrappedCFDictionaryForInterface as NSDictionary) as? [String: AnyObject] else {
print("System error: interface information is not a string-keyed dictionary")
return false
}
for d in SSIDDict.keys {
print("\(d): \(SSIDDict[d]!)")
}
}
return true
}
You may have seen this feature in jailbroken apps as it is possible to do this using private libraries, which means that apps that are sold on the iOS store can't be sold if they utilise them.
Related
I have two BluetoothHFP bluetooth devices connected to my iPad(bluetoothA2DP and bluetoothLE) and I need to detect which one is currently getting the audio. Below is the code I am using to detect what bluetooth is available :
let currentRoute = audioSession.currentRoute
for description in currentRoute.outputs {
if convertFromAVAudioSessionPort(description.portType) == convertFromAVAudioSessionPort(AVAudioSession.Port.bluetoothA2DP) {
//Do Something
break
}else if convertFromAVAudioSessionPort(description.portType) == convertFromAVAudioSessionPort(AVAudioSession.Port.bluetoothHFP) {
//Do Something
break
}else if convertFromAVAudioSessionPort(description.portType) == convertFromAVAudioSessionPort(AVAudioSession.Port.bluetoothLE){
//Do Something
break
}
}
What can I use to find out what one of the BluetoothHFP devices is currently getting audio?
You can distinguish between ports of the same type using description.portName and description.uid. Note that the name is not promised to be unique (it comes from the device). The UID is system-assigned and is not promised to be stable. It's only promised to be consistent with the owningPortUID property, and will be unique at any given time.
It happens to be true currently (iOS 13) that the UID is based on the hardware MAC address, and is stable. It's in the format aa:bb:cc:dd:ee:ff-tacl (the MAC address followed by -tacl). This has been true for a long time. I've been using this fact since at least iOS 8, and it's very likely it's been true as long as AVAudioSession has been around. But it's not promised, and is exactly the kind of thing that Apple has been known to change without notice.
"Low Data Mode" is introduced in iOS 13. See "Settings" section Apple's iOS 13 overview:
I couldn't find any developer documentation on this.
Is this something third party app developers can opt into, as suggested by MacRumors? Or is will it just suspend background activity when not connected to Wi-Fi, as suggested by AppleInsider?
To determine if iOS is currently in Low Data mode, you can use the Network library:
import Network // Put this on top of your class
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
if path.isConstrained {
// Path uses an interface in Low Data Mode.
}
else if path.isExpensive {
// Path uses an interface that is considered expensive, such as Cellular or a Personal Hotspot.
}
}
monitor.start(queue: DispatchQueue.global(qos: .background))
URLSession supports LowData mode in iOS 13.
Steps
Provide two different resources for high resolution and low resolution(low data mode)
If statusCode == 200 (low data mode is disabled in setting).
If error.networkAvailableReason == .constrained (low data mode is enable in settings)
Check out Advances in Networking, Part 1 from WWDC 2019 from 16:00 for a demo and sample code. You can use Combine to make the code simpler, but this is not required.
First of all, you need to configure your URLSession (NSURLSession) to either allow or disallow expensive or constrained network connections to be opened.
You can do that by changing URLSession's respective properties allowsExpensiveNetworkAccess or allowsConstrainedNetworkAccess to false (NO).
If a URLSessionTask results in an error that is either an NSURLErrorNotConnectedToInternet error containing NSURLErrorNetworkUnavailableReasonKey entry in userInfo (Objective-C) or an URLError with a non-nil NetworkUnavailableReason property set (Swift), then you need to act accordingly.
Those reasons can be:
expensive
constrained
cellular
The cellular reason has sort of been there since iOS 7, so it's not new, but the entire reason enumeration is, as Apple streamlined connection type handling a bit this year.
Here is the solution in Xamarin, for those interested:
NWPathMonitor monitor = new NWPathMonitor();
monitor.SetUpdatedSnapshotHandler(path =>
{
if (path.Status == NWPathStatus.Satisfied)
{
if(path.IsConstrained)
{
// Path uses an interface in Low Data Mode.
}
}
});
monitor.SetQueue(CoreFoundation.DispatchQueue.DefaultGlobalQueue);
monitor.Start();
We are using CallKit framework to benefit native usage for Voip features. Users can make Voice and Video Calls in our Messenger App.
But Apple removing CallKit apps from China, because of Chinese government.
What is the best approach for CallKit apps like us for now?
We do not want to remove our app from China and we do not remove all CallKit functionality from our app because of China..
I agree with txulu that it seems that CallKit just needs to be disabled/not used for users in China - see this helpful response on the Apple Developer forums.
The general consensus seems to be that as long as you can explain to App Review how you’re disabling CallKit features for users in China, that should probably be acceptable unless/until Apple publishes specific guidelines.
For your particular problem Ahmet, it sounds like CallKit may provide some of the the core functionality of your app. If this is the case and you really need to support users in China, you might want to look at rebuilding your app using another VOIP framework to make calls (VOIP is still allowed in China...just not using CallKit). Or perhaps you could disable and hide the calling features in your app if the user is in China.
My app was only using CallKit to observe when a call initiated from my app ends, so I was able to devise a work around. For users in China I now observe for the UIApplicationDidBecomeActiveNotification and make my best guess about whether a phone call initiated from the app has ended based on how much time has elapsed since the call began. It's not as good as using CallKit's CXCallObserver, but it seems to work well enough for my purpose.
Update! My app passed App Store review with the fix described.
Submitted a new version yesterday.
Included a short message in the reviewer info section saying "In this version and onwards, we do not use CallKit features for users in China. We detect the user's region using NSLocale."
App was approved around 12hr later without any questions or comments from the App Review team.
Detecting users in China
To determine if a user is in China, I am using NSLocale to get the users' currentLocale and countryCode. If the countryCode contains one of the ISO codes for China (CN, CHN), I set a flag to note I cannot use CallKit and not initialize or use CallKit features in my app.
- (void)viewDidLoad {
[super viewDidLoad];
NSLocale *userLocale = [NSLocale currentLocale];
if ([userLocale.countryCode containsString: #"CN"] || [userLocale.countryCode containsString: #"CHN"]) {
NSLog(#"currentLocale is China so we cannot use CallKit.");
self.cannotUseCallKit = YES;
} else {
self.cannotUseCallKit = NO;
// setup CallKit observer
self.callObserver = [[CXCallObserver alloc] init];
[self.callObserver setDelegate:self queue:nil];
}
}
To test this, you can change the region in Settings > General > Language and Region > Region. When I set Region to 'China' but left language set as English, [NSLocale currentLocale] returned "en_CN".
Swift 5
Utility Functions
func isCallKitSupported() -> Bool {
let userLocale = NSLocale.current
guard let regionCode = userLocale.regionCode else { return false }
if regionCode.contains("CN") ||
regionCode.contains("CHN") {
return false
} else {
return true
}
}
MainViewController
class MainViewController: UIViewController {
...
var callObserver = CXCallObserver()
...
override func viewDidLoad() {
super.viewDidLoad()
if isCallKitSupported() {
callObserver.setDelegate(self, queue: nil)
}
...
}
...
}
Note: countryCode is now regionCode and only returns 'US', 'CN', etc. No language before country code like 'en_CN'.
Swift 5
func isCallKitSupport() -> Bool {
let userLocale = NSLocale.current
if userLocale.regionCode?.contains("CN") != nil ||
userLocale.regionCode?.contains("CHN") != nil {
return false
} else {
return true
}
}
One thing you could try, even though it may not work: disable callkit functionality based on the locale region. This may be enough "proof" that Callkit is disabled for China from the legal perspective in order to be approved for the Appstore. Then your Chinese customers could just switch the region in the settings to get Callkit. This would be already "their" problem so to speak.
Disclaimer: I'm by no means a lawyer or anything, follow this advice at your own risk.
Edit:
CXProvider.isSupported is no longer available: I keep the answer here hoping that it will be restored back on an upcoming iOS 13 release.
From iOS 13 onwards, the correct way to do this is to check the new CXProvider.isSupported property.
Here's the documentation (from Xcode, as the online documentation has not been updated yet):
Go to “Pricing and Availability” in iTunes Connect.
Availability” (Click blue button Edit).
Deselect China in the list “Deselect” button.
Click “Done”.
I'm using ARKit but it is not a core functionality in my app, so I'm not setting the arkit key in the UIRequiredDeviceCapabilities. I'm setting the directive #available(iOS 11.0, *), but ARKit requires an A9 processor or above (that is, iPhone 6S or newer...)
What the best way to check that? I've found a workaround that involves checking the device model in several places, but it looks like it's a bit complicated. And would this be rejected in a review for the Store?
You should check the isSupported boolean provided by the ARConfiguration class for this.
From the Apple Developer Documentation:
isSupported
A Boolean value indicating whether the current device supports this session configuration class.
All ARKit configurations require an iOS device with an A9 or later processor. If your app otherwise supports other devices and offers augmented reality as a secondary feature, use this property to determine whether to offer AR-based features to the user.
Just check ARConfiguration availability.
if (ARConfiguration.isSupported) {
// Great! let have experience of ARKIT
} else {
// Sorry! you don't have ARKIT support in your device
}
Here is the solution Ar support or not
// Get array containing installed apps
var installedApps = require("react-native-installed-packages");
let appArray = installedApps.getApps();
// Check app_array for ar core package
var arPackage = appArray.find(function (_package) {
return _package == "com.google.ar.core";
});
if (typeof arPackage === "undefined") {
console.log("AR not support");
} else {
console.log("AR support");
}
I'm hoping another developer has come across this somewhere. I have an app I have been trying to get approved by Apple. The interesting thing is the app behaves differently while running locally on my iPhone and in the iOS Simulator. Specifically when a new user signs up I am checking a flag as to if the Users have agreed to the apps Terms and Conditions. In Parse the column is set to undefined until they agree then it goes to true. This works fine in all my testing. But at Apple it is not and is not triggering the Agree to Terms condition. Here is the code for that check:
if((PFUser.currentUser()!["termsAgreed"]) != nil) {
termsAgreed = PFUser.currentUser()!["termsAgreed"] as! Bool
if !termsAgreed {
self.performSegueWithIdentifier("agreeTerms", sender: nil)
}
}
else {
self.performSegueWithIdentifier("agreeTerms", sender: nil)
I made a new change to see if it helps, in that when a user signs up I set PFUser.currentUser()!["termsAgreed"] to false before this check
I'm hoping that enforces the terms agreement condition to fail on Apples end but would like some feedback from anyone experiencing something like this.
Depending on what your actual question might be, using force unwrapping is not considered quite safe. Your code would be much more stable like this:
if let termsAgreed = PFUser.currentUser()?["termsAgreed"] as? Bool where
!termsAgreed {
self.performSegueWithIdentifier("agreeTerms", sender: nil)
}
else {
// proceed
}
PS: I hope that is syntactically correct - I have no IDE at hand atm.