IOS restrict access to App for certain Country or WLAN - ios

I'd like to develop an app which is only available in a germany.
Preferably if a user downloads the app and leaves the germany, it should not work anymore. Is this possible?
Since i only want to know the country the user is currently i don't want to ask for the whole location.
Furthermore I'd like to restrict some content to only a specific WLAN Network. How can I do that?

Unfortunately, there is no build-in geofance on placemark level.
You could use region monitoring, but this would require to re-construct the borders of Germany as (up to 20) CLCircularRegion objects. After that, you just let the core location framework inform you if a region changes.
The drawback would be that you can only use 20 circular regions to cover the area of Germany, so you'll lose a little of precision.
A second solution would be to use a timer based check:
get the current user location
use CLGeocoder.reverseGeocodeLocation, which will provide the CLPlacemark.country
check if we are still in Germany

You can use:
NSString *countryCode = [[NSLocale currentLocale] objectForKey: NSLocaleCountryCode];
To get the user's country code. The country code for Germany is DE.
You can then check the user's country code and if it isn't DE then lock the app by bringing up a screen saying the app is locked or something like that.
For the WLAN part of your question, you can get the name of the network the user is connected to:
How to get available all wifi network name Listing in iOS using swift
This class will show only the wifi network name you are connected to -
import UIKit
import SystemConfiguration.CaptiveNetwork
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad(){
super.viewDidLoad()
let ssid = self.getAllWiFiNameList()
print("SSID: \(ssid)")
}
func getAllWiFiNameList() -> String? {
var ssid: String?
if let interfaces = CNCopySupportedInterfaces() as NSArray? {
for interface in interfaces {
if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? {
ssid = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String
break
}
}
}
return ssid
}
}
You can check if that class is not equal to a pre-set class to block some functionality.

Related

How to shield the categories with Family Controls in iOS

I've implemented shielding apps with Family Controls.
But I don't know how to shield all categories app except the app selected by user in the FamilyActivityPicker? Here is my code to shield apps and it worked. But I want to know if I could shield all apps and all "categories" except the apps user selected. I want to allow only the apps and categories that user has selected.
let store = ManagedSettingsStore()
if let object = UserDefaults.standard.object(forKey: "SelectedAppTokens") as? Data {
let decoder = JSONDecoder()
if let appTokens = try? decoder.decode(Set<ApplicationToken>.self, from: object) {
store.shield.applicationCategories = ShieldSettings.ActivityCategoryPolicy.all(except: appTokens)
}
}
There is api to shield all "app" except user selected, but no "category"
I found a solution. I can get the Application Tokens from category when I set the option includeEntireCategory to true in FamilyActivitySelection.
like this
#State var selection = FamilyActivitySelection(includeEntireCategory: true)
If then, I can save the all application tokens(category include) selected by user.

Run code for existing users only - SwiftUI/iOS/Swift

I have a production-ready app where I need to run some code only on users with the previous versions installed but not on new installations. For instance, if 1.0 is the latest version in the AppStore and 2.0 is the new one that will introduce code that needs to run only on users with version 1.0 but not on new users.
e.g.
if isExistingUser{
// run code
}
What would be the best way to run some code only for existing users? How can one determine whether it's a new or existing user?
Does your app create any data? Maybe files in the Documents directory, or maybe a UserDefaults key? If so, check for the presence of one of those things very early in launch, which will signal to you that this must be an upgrade and you should do your thing.
A lot of people store the app's CFBundleShortVersionString Info.plist key into UserDefaults at launch, which makes it easy to know the last version that was run and let you write the logic of what needs to happen to migrate from that version to the new version. This is something you might want to think about doing in future versions.
I see this often and knowing how to do this can be extremely valuable, especially when doing something like introducing a new feature that changes the experience your previous users had.
There are a few approaches you can take depending on your needs.
Firstly, you could create a boolean variable in your user model class that is set during user registration in the standard user registration flow and indicates that this is a newly created user - and name it something like isNewOnVersionTwo which will indicate this user is a new user on this new version.
Code Example:
class User: Decodable {
var uid: string!
var username: string!
var isNewOnVersionTwo: Bool = false
}
class RegistrationViewController: UIViewController {
var user: User!
var isNewOnVersionTwo: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
user.isNewOnVersionTwo = true
}
}
class HomeViewController: UIViewController {
var user: User!
override func viewDidLoad() {
super.viewDidLoad()
isNewOnVersionTwo == true ? normalInit() : showOldUserAView()
}
func normalInit() {
// Run normal functions for the 'HomeViewController' or do nothing.
}
func showOldUserAView() {
//Do something specific for only old users.
}
}
You can choose whether you want to hold onto this variable permanently or not - it could be useful for tracking the new users you've gained on this version versus the previous versions - and you could send it to your database along with the rest of the data from your user model.
A second and cleaner approach...
Could be to only set the boolean on the very last view controller of the registration flow and pass it to the home view controller when you push the user to the view controller.
Like this:
class ViewControllerOne: UIViewController {
var isNewOnVersionTwo: Bool = false
private func pushToNewViewController() {
let vc = HomeViewController()
vc.isNewOnVersionTwo = true
navigationController?.pushViewController(vc, animated: true)
}
}
class HomeViewController: UIViewController {
var isNewOnVersionTwo: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
isNewOnVersionTwo == true ? normalInit() : showOldUserAView()
}
func normalInit() {
// Run normal functions for the 'HomeViewController' or do nothing.
}
func showOldUserAView() {
//Do something specific for only old users.
}
}
I wouldn't take the approach of using UserDefaults for the few following reasons:
1. Devices occasionally wipe this randomly and it's not as reliable as hardcoding a flag in the source code.
2. UserDefaults isn't available for a few moments on cold app launches / initial startup which can complicate things, varying on when you want to show the older users whatever you want to show them.
Here is what I came up with that I think works for 95-98% of the users. What I'm doing is basically comparing the date when the Documents folder was created and the date when version2 will be released. The only issue I see with this method is for users installing the app between the date you specified as the release date and the actual App Store release date. In other words, if you specify a date of June, 5 as the release date but the app isn't really approved until the 7th, users who installed the app on the 6th will be missed.
Ideally and it's what I will start doing is what #Rudedog suggested, basically saving the versions to keep track of what version a user has.
/// - Returns: Returns true if it's a new user otherwise returns false.
func isExistingUser()->Bool{
var isExistingUser:Bool?
var appInstallationDate:Date?
/// Set the date when the version two is released.
var dateComponents = DateComponents()
dateComponents.year = 2022
dateComponents.month = 06
dateComponents.day = 4
let userCalendar = Calendar.current
let versionsTwoReleaseDate = userCalendar.date(from: dateComponents)
/// Get the date of when the documents folder was created to determine when the app was installed.
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
if let creationDate = (try? documentsDirectory!.resourceValues(forKeys: [.creationDateKey]))?.creationDate{
appInstallationDate = creationDate
}
/// Compare dates
if appInstallationDate! < versionsTwoReleaseDate!{
// print("It's existing user")
isExistingUser = true
}else{
// print("It's NEW user")
isExistingUser = false
}
return isExistingUser!
}

How to add AppClip to Siri's Nearby Suggestions?

In the video Configure and link your app clips Apple shows it's possible to have your AppClip being suggested by Siri based on the user's location.
I can't find this in the docs https://developer.apple.com/documentation/app_clips
Is this purely based on which location other users are using this app, or is this something a developer can configure (maybe based on a geocode region)?
According to an Apple App Clips engineer I spoke to at WWDC, in order to get your App Clip published onto Apple Maps, you need to have a registered business registered with Apple. This can be done since iOS 10, via Apple Maps Connect, and registering as a small business.
The Nearby Siri suggestion is based on location data and only appears when your App Clip is associated with a place card on Apple Maps so you do not have control over this. It's definitely possible to get the user location after downloading the App Clips, as showed in the following demo but from your question I presumed you wanted to present the App Clip suggestion before downloading (isn't in the developer's control).
If you want to register an App Clip to a location, you need to wait till App Clips are fully usable and publishable on the App Store. When TestFlight and App Store Connect get support for app clips later this year, you'll be able to invoke an app clip from NFC, QR codes, Maps and more. So you would need to register your business with Apple, register your placecard in Apple Maps and then enable App Clips to get the suggestion.
There is a sample code documentation page that has Widgets and App Clip in the code: https://developer.apple.com/documentation/swiftui/fruta_building_a_feature-rich_app_with_swiftui
In the link above in the App Clip code section, there is a payload that has latitude and longitude configurable. Siri should automatically suggest the App Clip based on the location you put in the latitude and longitude.
#if APPCLIP
func handleUserActivity(_ userActivity: NSUserActivity) {
guard let incomingURL = userActivity.webpageURL,
let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
let queryItems = components.queryItems else {
return
}
if let smoothieID = queryItems.first(where: { $0.name == "smoothie" })?.value {
model.selectSmoothie(id: smoothieID)
}
guard let payload = userActivity.appClipActivationPayload,
let latitudeValue = queryItems.first(where: { $0.name == "latitude" })?.value,
let longitudeValue = queryItems.first(where: { $0.name == "longitude" })?.value,
let latitude = Double(latitudeValue), let longitude = Double(longitudeValue) else {
return
}
let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: latitude,
longitude: longitude), radius: 100, identifier: "smoothie_location")
payload.confirmAcquired(in: region) { inRegion, error in
if let error = error {
print(error.localizedDescription)
return
}
DispatchQueue.main.async {
model.applePayAllowed = inRegion
}
}
}
#endif
The code snippet is from the documentation from link above.
The documentation doesn't go in specific details yet but from what you can read here:
https://developer.apple.com/documentation/app_clips
and here https://developer.apple.com/documentation/app_clips/configuring_your_app_clip_s_launch_experience
under "Review Advanced App Clip Experiences"
You should be able to associate your app clip with a physical location, which will be available in AppStore Connect and with this set, Siri suggestions should be able to pick up your App Clip based on the user location

How to get all previously connected Wi-Fi networks in Swift?

I know it's not possible to get all available Wi-Fi networks and it's possible to get the information of current connected Wi-Fi network, correct me if I'm wrong.
I just want to know the feasibility of getting the information of all previously connected Wi-Fi networks, Please help me by sharing your experience.
FYI,
I'm using the following code get current connected Wi-Fi network's info and it's working fine.
var currentSSID: String!
let interfaces:CFArray! = CNCopySupportedInterfaces()
for i in 0..<CFArrayGetCount(interfaces) {
let interfaceName: UnsafePointer<Void>
= CFArrayGetValueAtIndex(interfaces, i)
let rec = unsafeBitCast(interfaceName, AnyObject.self)
let unsafeInterfaceData = CNCopyCurrentNetworkInfo("\(rec)")
if unsafeInterfaceData != nil {
let interfaceData = unsafeInterfaceData! as Dictionary!
currentSSID = interfaceData["SSID"] as! String
} else {
currentSSID = ""
}
print(currentSSID)
}
From a stock iPhone this is not possible from the settings. The information that you want is stored in the iPhone's keychain. If you have access to the iPhone SDK you can start messing around if you like, but I will leave it at that since that would be a StackOverflow answer. Source
If you have ICloud Keychain enabled, it is possible to fetch this data. Otherwise, you can't. Source You can use kishikawakatsumi/KeychainAccess or do it yourself. Here is homemade code to save and retrieve keychain values.
It is a simple swift wrapper for Keychain that works on iOS and OS X

iPhone get a list of all SSIDs without private library

Is it possible the get a list of all available SSIDs on the iPhone without using a private library?
I read iPhone get SSID without private library which is about getting details about the current network.
This answer mentions:
If you jailbreak your device you can use the Apple80211 private framework to look up the available Wi-Fi networks and their signal strength. But that also means your app will get rejected.
Apple has the CaptiveNetwork API but there doesn't seem to be a solution to get a list of all available networks. It seems it's only possible to do so with using the Apple80211 private library, or connecting to all of them.
Am I missing something, or is there no solution?
Without the use of private library (Apple80211) you can only get the SSID of the network your device is currently connected to.
Since iOS 9, you can use NEHotspotHelper to get a list of SSIDs. But you have to get the com.apple.developer.networking.HotspotHelper entitlement from Apple by sending a request.
Check https://developer.apple.com/documentation/networkextension/nehotspothelper for more infomation.
Some new APIs have been released as part of the Network Extension in iOS 9 and iOS 11. While neither allows you to scan for networks while your app is running, they both allow you to do related tasks. E.g. you can scan for networks while the Settings Wi-Fi page is running using Hotspot Helper, and you can make it easier for a user to join a network using either of these.
Here's a comparison of the two frameworks.
Hotspot Helper
NEHotspotHelper (introduced in iOS 9, WWDC 2015).
Requires special permission from Apple.
Requires the com.apple.developer.networking.HotspotHelper entitlement.
For step-by-step instructions to get this working, see this answer.
Allows you to participate in the discovery/authentication to a Wi-Fi network via the Wi-Fi screen in the Settings app. You register to be notified when networks are being scanned (e.g. when the user launches Wi-Fi in the Settings app), and you can automatically pre-fill the password and display an annotation near the network name. The user still needs to tap on the network name to connect, but it won't prompt for a password if you pre-filled it.
Hotspot Configuration
NEHotspotConfigurationManager (introduced in iOS 11, WWDC 2017).
Does not require special permission from Apple.
Requires the com.apple.developer.networking.HotspotConfiguration entitlement.
Allows you to initiate a connection to a Wi-Fi network. You give it a list of SSIDs/Passwords that should be connected to while your app is running. It will present a dialog asking the user if they want to connect to the network.
Step 1: add the framework SystemConfiguration.framework
Step 2: import following header file
import SystemConfiguration
import SystemConfiguration.CaptiveNetwork
Step 3: Now Use Code:
func getUsedSSID()->String {
let interfaces = CNCopySupportedInterfaces()
if interfaces != nil {
let interfacesArray = CFBridgingRetain(interfaces) as! NSArray
if interfacesArray.count > 0 {
let interfaceName = interfacesArray[0] as! String
let unsafeInterfaceData = CNCopyCurrentNetworkInfo(interfaceName)! as Dictionary
let SSIDName = unsafeInterfaceData["SSID"] as! String
print(SSIDName)/* here print recentally used wifi name*/
return SSIDName
}else{
return "0"
}
}else{
return "0"
}
}
First of All import above two system Header file
import SystemConfiguration/SystemConfiguration.h
import SystemConfiguration/CaptiveNetwork.h
below Function/Method Return SSIDName
-(NSString *)getNetworkId{
NSString *string = CFBridgingRelease(CNCopySupportedInterfaces());
NSArray *interfacesArray = CFBridgingRelease(CFBridgingRetain(string));
if(interfacesArray.count > 0){
NSString *networkName = [interfacesArray objectAtIndex:0];
CFStringRef yourFriendlyCFString = (__bridge CFStringRef)networkName;
NSDictionary *unsafeInterfaceData = CFBridgingRelease(CNCopyCurrentNetworkInfo(yourFriendlyCFString));
NSString *ssidName = unsafeInterfaceData[#"SSID"];
return ssidName;
}
return #"No network Found";
}
#import SystemConfiguration#
##import SystemConfiguration.CaptiveNetwork##
//create variable
var SSIDNameArray = NSMutableArray()
var nameArray : NSArray = [];
// Here function to return all SSIDName
func getUsedSSID()->NSArray{
let interfaces = CNCopySupportedInterfaces()
if interfaces != nil {
let interfacesArray = CFBridgingRetain(interfaces) as! NSArray
if interfacesArray.count > 0 {
for interfaceName in interfacesArray {
let unsafeInterfaceData = CNCopyCurrentNetworkInfo(interfaceName as! CFString)! as NSDictionary
let SSIDName = unsafeInterfaceData["SSID"] as! String
self.SSIDNameArray .add(SSIDName)
}
nameArray = self.SSIDNameArray .copy() as! NSArray
return nameArray;
}else{
return nameArray;
}
}else{
return nameArray;
}
}

Resources