How to get the user agent on iOS? - iphone

Is there a way on iOS to get the user agent of the device? I don't want to hard code it since I need the user agent for all devices and I need to append the user agent to a URL.
Thanks.

A simpler way to ascertain the user agent in iOS is to get it directly from a UIWebView using the accepted answer to this SO post. To quote that answer:
The solution was to create a UIWebView and then just use javascript to pull out the user agent.
UIWebView* webView = [[UIWebView alloc] initWithFrame:CGRectZero];
NSString* secretAgent = [webView stringByEvaluatingJavaScriptFromString:#"navigator.userAgent"];

Mobile application in every request must send his User-Agent header with build version and device information
User agent configuration info
So, user agent should be like:
User-Agent: <AppName>/version (<system-information>) <platform> (<platform-details>) <extensions>
For iOS:
User-Agent: <AppName/<version> (<iDevice platform>; <Apple model identifier>; iOS/<OS version>) CFNetwork/<version> Darwin/<version>
How to get each of the components?
Headers Key - u can hardcode or use some constant values
AppName and version - grab from Info.plist
let infoPlist = try? PListFile<InfoPlist>()
let appName = infoPlist.data.bundleName
let version = infoPlist.data.versionNumber
let build = infoPlist.data.buildNumber
Info about Device
modelName - you can obtain like described here
let modelName = UIDevice.current.modelName
other components:
let platform = UIDevice.current.systemName
let operationSystemVersion = ProcessInfo.processInfo.operatingSystemVersionString
CFNetwork version
static var cfNetworkVersion: String? {
guard
let bundle = Bundle(identifier: "com.apple.CFNetwork"),
let versionAny = bundle.infoDictionary?[kCFBundleVersionKey as String],
let version = versionAny as? String
else { return nil }
return version
}
from here
Darwin Version
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.release)
let darvinVersionString = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8,
value != 0 else {
return identifier
}
return identifier + String(UnicodeScalar(UInt8(value)))
}
from here
Result:
MyApp/1.8.199 (iOS; iPhone XS; Version 13.3 (Build 17C45)) CFNetwork/1121.2.1 Darvin/19.3.0

You don’t actually need to make the request in order to get the user-agent. Just return NO from the following delegate method and retain the user-Agent header:
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
It might look something like this:
-(BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
userAgent = [[request valueForHTTPHeaderField:#"User-Agent"] copy];
NSLog(#"user-agent: %#", userAgent);
_webView.delegate = nil;
[_webView release];
return NO;
}

(iOS 8.0, *)
Since UIWebView is deprecated in iOS 12, you should use WKWebView instead.
Since WKWebView.evaluateJavaScript(_:) result is now async, this implementation solve a common requirement to have userAgent available in your own REST api call.
import WebKit
class UAString {
static var userAgent : String = ""
#discardableResult init(view parent: UIView) {
if UAString.userAgent.isEmpty {
let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
webView.translatesAutoresizingMaskIntoConstraints = false
parent.addSubview(webView)
webView.evaluateJavaScript("navigator.userAgent") { result, _ in
UAString.userAgent = result as? String ?? ""
}
}
}
}
Now last part you can implement this class in your initial view controller as follow:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UAString(view: self.view)
}
then you can access the attribute as UAString.userAgent

Objective-C
WKWebView *webView = [WKWebView new];
res = [webView valueForKey:#"userAgent"];
Swift
let ua = WKWebView().value(forKey: "userAgent")
Dont forget to import WebKit 😂

Swift 3.x, 4.x, 5.x, & above
As sometime traditional UIWebView get's memory leaked so instead use always WKWebView (far better from UIWebView)
import WebKit
var webViewForUserAgent: WKWebView?
and get userAgent by calling below function & you can also set it to your other variable
func getUserAgent() {
webViewForUserAgent = WKWebView() // must initialize
webViewForUserAgent?.evaluateJavaScript("navigator.userAgent") { (result, error) in
//
if error != nil {
print("Error occured to get userAgent")
return
}
//
if let unwrappedUserAgent = result as? String {
print("userAgent: \(unwrappedUserAgent)")
} else {
print("Failed to get userAgent")
}
}
}

A simpler way to ascertain the user agent in iOS is to get it directly from a UIWebView using the accepted answer to this SO post.But this way has two disadvantages:
1、UIWebView's first allocation may take too much time in initializing webview context.
2、the code must be executed in main thread. This may stuck main thread.
If you know the tricks of how to use private methods while avoiding the refusal of App Store Review.
You can try the following code:
#define CALL_PRIVATE_INSTANCEMETHOD(x,sel,q)\
{\
SEL selector = NSSelectorFromString([NSString stringWithFormat:#"%#",##sel]);\
if ([x respondsToSelector:selector]) {\
_Pragma("clang diagnostic push")\
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")\
q=[x performSelector:selector];\
_Pragma("clang diagnostic pop")\
}\
}\
#define CALL_PRIVATE_CLASSMETHOD_ONEPARAM(x,sel,p,q)\
{\
SEL selector = NSSelectorFromString([NSString stringWithFormat:#"_%#:",##sel]);\
if ([x respondsToSelector:selector]) {\
_Pragma("clang diagnostic push")\
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")\
q=[x performSelector:selector withObject:p];\
_Pragma("clang diagnostic pop")\
}\
}\
+ (NSString *)standardUserAgent{
NSString *buildVersion = nil;
CALL_PRIVATE_INSTANCEMETHOD([UIDevice currentDevice], buildVersion,buildVersion);
Class webViewCls = NSClassFromString([NSString stringWithFormat:#"%#%#",#"Web",#"View"]);
NSString *standardUA = nil;
NSString *versions = [NSString stringWithFormat:#"Mobile/%#",buildVersion];
CALL_PRIVATE_CLASSMETHOD_ONEPARAM(webViewCls, standardUserAgentWithApplicationName,versions,standardUA);
return standardUA;
}

Related

Open containing app from custom keyboard in Swift 3?

Before iOS 10 i used the method written by Valentin Sherwin here.
Any workarounds to open the containing app from the custom keyboard with Swift 3?
please try to use below method.
it worked well on xcode 8.2, swift 3.0
func openURL(_ url: URL) {
return
}
func openApp(_ urlstring:String) {
var responder: UIResponder? = self as UIResponder
let selector = #selector(openURL(_:))
while responder != nil {
if responder!.responds(to: selector) && responder != self {
responder!.perform(selector, with: URL(string: urlstring)!)
return
}
responder = responder?.next
}
}
// Usage
//call the method like below
// self.openApp(urlString)
// URL string need to included custom scheme.
// for example, if you created scheme name = customApp
// urlString will be "customApp://?[name]=[value]"
// self.openApp("customApp://?category=1")

Open calendar from Swift app

How can I open a calendar from Swift app (when pressing a button for example)? Or is there a way to embed a calendar in a view controller in the app?
I want to avoid using external calendars programmed by others. Thanks!
You can open the Calendar app by using the url scheme calshow://:
Swift 3+
guard let url = URL(string: "calshow://") else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
Swift 2 and below
UIApplication.sharedApplication().openURL(NSURL(string: "calshow://")!)
With EventKit, you can implement your self a calendar. You should read Calendar and Reminders Programming Guide from Apple site.
openURL Deprecated in iOS10
From Apple’s guide to What’s New in iOS in the section on UIKit:
The new UIApplication method openURL:options:completionHandler:, which
is executed asynchronously and calls the specified completion handler
on the main queue (this method replaces openURL:).
Swift 3
func open(scheme: String) {
if let url = URL(string: scheme) {
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:],
completionHandler: {
(success) in
print("Open \(scheme): \(success)")
})
} else {
let success = UIApplication.shared.openURL(url)
print("Open \(scheme): \(success)")
}
}
}
// Typical usage
open(scheme: "calshow://")
Objective-C
- (void)openScheme:(NSString *)scheme {
UIApplication *application = [UIApplication sharedApplication];
NSURL *URL = [NSURL URLWithString:scheme];
if ([application respondsToSelector:#selector(openURL:options:completionHandler:)]) {
[application openURL:URL options:#{}
completionHandler:^(BOOL success) {
NSLog(#"Open %#: %d",scheme,success);
}];
} else {
BOOL success = [application openURL:URL];
NSLog(#"Open %#: %d",scheme,success);
}
}
// Typical usage
[self openScheme:#"calshow://"];
Note:- Don't forgot to add privacy usage description in your info.plist file.,if you are trying to open any system app then in iOS 10+ you need to specify privacy usage description in your info.plist file else your app get crash.
As HoaParis already mentioned, you can call the calendar by using the openURL method.
There is no embedded calendar by apple by default but you could check out other calendars for example the open-source one CVCalendar which is available at github. So you could either use it in your project or check how the developer has coded the calendar.

How to use openURL for making a phone call in Swift?

I have converted the code for making a phone call from Objective-C to Swift, but in Objective-C, we can set the type of the URL that we like to open (e.g. telephone, SMS, web) like this:
#"tel:xx"
#"mailto:info#example.es"
#"http://stackoverflow.com"
#"sms:768number"
The code in Swift is:
UIApplication.sharedApplication().openURL(NSURL(string : "9809088798")
I read that have not released any scheme parameter for tel:, but I don't know if Swift can detect if the string is for making a phone call, sending email, or opening a website. Or may I write:
(string : "tel//:9809088798")
?
I am pretty sure you want:
UIApplication.sharedApplication().openURL(NSURL(string: "tel://9809088798")!)
(note that in your question text, you put tel//:, not tel://).
NSURL's string init expects a well-formed URL. It will not turn a bunch of numbers into a telephone number. You sometimes see phone-number detection in UIWebView, but that's being done at a higher level than NSURL.
EDIT: Added ! per comment below
A self-contained solution in Swift:
private func callNumber(phoneNumber:String) {
if let phoneCallURL:NSURL = NSURL(string:"tel://\(phoneNumber)") {
let application:UIApplication = UIApplication.sharedApplication()
if (application.canOpenURL(phoneCallURL)) {
application.openURL(phoneCallURL);
}
}
}
Now, you should be able to use callNumber("7178881234") to make a call.
For Swift in iOS:
var url:NSURL? = NSURL(string: "tel://9809088798")
UIApplication.sharedApplication().openURL(url!)
You need to remember to remove the whitespaces or it won't work:
if let telephoneURL = NSURL(string: "telprompt://\(phoneNumber.stringByReplacingOccurrencesOfString(" ", withString: ""))") {
UIApplication.sharedApplication().openURL(telelphoneURL)
}
"telprompt://" will prompt the user to call or cancel while "tel://" will call directly.
# confile:
The problem is that your solution does not return to the app after the
phone call has been finished on iOS7. – Jun 19 at 13:50
&# Zorayr
Hm, curious if there is a solution that does do that.. might be a
restriction on iOS.
use
UIApplication.sharedApplication().openURL(NSURL(string: "telprompt://9809088798")!)
You will get a prompt to Call/Cancel but it returns to your application. AFAIK there is no way to return (without prompting)
You must insert "+"\ is another way
private func callNumber(phoneNumber:String) {
if let phoneCallURL:NSURL = NSURL(string:"tel://"+"\(phoneNumber)") {
let application:UIApplication = UIApplication.sharedApplication()
if (application.canOpenURL(phoneCallURL)) {
application.openURL(phoneCallURL);
}
}
}
Small update to Swift 3
UIApplication.shared.openURL(NSURL(string: "telprompt://9809088798")! as URL)
The following code snippet can tell if the SIM is there or not and if the device is capable of making the call and if ok then it'll make the call
var info = CTTelephonyNetworkInfo()
var carrier = info.subscriberCellularProvider
if carrier != nil && carrier.mobileNetworkCode == nil || carrier.mobileNetworkCode.isEqual("") {
//SIM is not there in the phone
}
else if UIApplication.sharedApplication().canopenURL(NSURL(string: "tel://9809088798")!)
{
UIApplication.sharedApplication().openURL(NSURL(string: "tel://9809088798")!)
}
else
{
//Device does not have call making capability
}
For making a call in swift 5.1, just use the following code: (I have tested it in Xcode 11)
let phone = "1234567890"
if let callUrl = URL(string: "tel://\(phone)"), UIApplication.shared.canOpenURL(callUrl) {
UIApplication.shared.open(callUrl)
}
Edit:
For Xcode 12.4, swift 5.3, just use the following:
UIApplication.shared.open(NSURL(string: "tel://555-123-1234")! as URL)
Make sure that you import UIKit, or it will say that it cannot find UIApplication in scope.
For swift 3
if let phoneCallURL:URL = URL(string:"tel://\(phoneNumber ?? "")") {
let application:UIApplication = UIApplication.shared
if (application.canOpenURL(phoneCallURL)) {
application.open(phoneCallURL, options: [:], completionHandler: nil);
}
}
For swift 4:
func call(phoneNumber: String) {
if let url = URL(string: phoneNumber) {
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:],
completionHandler: {
(success) in
print("Open \(phoneNumber): \(success)")
})
} else {
let success = UIApplication.shared.openURL(url)
print("Open \(phoneNumber): \(success)")
}
}
}
Then, use the function:
let phoneNumber = "tel://+132342424"
call(phoneNumber: phoneNumber)
Swift 4 and above
let dialer = URL(string: "tel://5028493750")
if let dialerURL = dialer {
UIApplication.shared.open(dialerURL)
}
I prefer deferring the URL creation to the built-ins like:
var components = URLComponents()
components.scheme = "tel"
components.path = "1234567890"
print(components.url!) //Prints out: "tel:1234567890"
Which can then be used in UIApplication.shared.openURL

Changing language on the fly, in running iOS, programmatically

I've been stackling and googling for hours. And I'm kind of desperate now.
I would like to change the language of my application inside the app not only with the default language.
From what I've tried I stuck like everybody with the reboot step. Meaning, apples forces you to restart the app manually. Meaning you have to quit the app and then starting it up again.
Well, after googling, I was trying to setup an alarm and then forcing later the app to exit with
exit(0);
My bad, apple seems not to like this and prevent developer from using it... I guess I'm not pointing in the right direction.
Finally, despite all the problem, I could meet I would like to discuss about that.
Any hints?
EDIT, infos from APPLE
In general, you should not change the
iOS system language (via use of the
AppleLanguages pref key) from within
your application. This goes against
the basic iOS user model for switching
languages in the Settings app, and
also uses a preference key that is not
documented, meaning that at some point
in the future, the key name could
change, which would break your
application.
If you want to switch languages in
your application, you can do so via
manually loading resource files in
your bundle. You can use
NSBundle:pathForResource:ofType:inDirectory:forLocalization:
for this purpose, but keep in mind
that your application would be
responsible for all loading of
localized data.
Regarding the exit(0) question, Apple
DTS cannot comment on the app approval
process. You should contact
appreview#apple.com to get an answer
for this question.
Well, I have to choose so far.
This is a fairly old question, but I was just struggling with the same problem and found this solution:
http://aggressive-mediocrity.blogspot.com/2010/03/custom-localization-system-for-your.html
Which does exactly what you need (and might be helpful for others who with the same problem :)
Below link has a nice implementation of having custom language from with in the application.
manual language selection in an iOS-App (iPhone and iPad)
Attempted a SWIFT version find it here
LanguageSettings_Swift
-anoop
yes, i had the same problem, then i managed it with my own language setting in my prefFile, where i set a variable for the language setting:
// write a new value in file and set the var
- (void)changeLangInPrefFile:(NSString *)newLanguage {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"myPreference.plist"];
NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
//here add elements to data file and write data to file
[data setObject:newLanguage forKey:#"language"];
[data writeToFile:path atomically:YES];
[data release];
// NSString *chosenLang; <- declared in .h file
if (chosenLang != nil){
[chosenLang release];
chosenLang = nil;
}
chosenLang = [[NSString alloc] initWithString:(#"%#",newLanguage)];
}
// read the language from file and set the var:
- (void)readFromFileInBundleDocuments {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"myPreference.plist"];
NSMutableDictionary *savedStock = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
NSString *chosenLangTemp = [savedStock objectForKey:#"language"];
NSLog (#"read in file: %#", chosenLangTemp);
if (chosenLang != nil){
[chosenLang release];
chosenLang = nil;
}
chosenLang = [[NSString alloc] initWithString:(#"%#",chosenLangTemp)];
[savedStock release];
}
then i load all the contents from different files depending on the language
for example i can load "an_image_eng.png" or "an_image_ita.png",
or have 2 different .xib file
and for the text to load i use different dictionary-files, one for each language, with all words/expressions translated, i just load the chosen one and read in it the right expression for every text to be load (the code to load it is similar to the method i wrote in this example, you can just arrange it to read the right word for every expression: just look at the value for objectForKey in the right dictionary file, where objectForKey is the word to translate and its value is the word translated)...
Generally, the language the user sees is determined by the locale setting, which is a system-wide setting. Only the user can change it, and when he does, SpringBoard and every running application on the device must restart. There is no way around this because all system apps and frameworks assume that the locale doesn't change once they start running. Changing the apps and frameworks to not require a relaunch would be very difficult for Apple to do.
I'm guessing that you either want to vary the language of your app's interface completely independently of the system locale setting, or you want to use the system locale setting by default but allow the user to override it for just your app.
You can get the current locale and examine its various values using +[NSLocale currentLocale]. To display your app's user interface in a language that is independent of the system locale, you'll need to avoid usage of NSLocalizedString() entirely, and use some sort of custom state of your own to determine which strings to display to the user and how to modify the interface to fit your app's language. It'll be up to you to keep your app's language state and modify its user interface appropriately.
This is an old question, but i was developing an helper that notifies me when the language change on the fly.
Take a look at the code of helper:
import Foundation
class LocalizableLanguage {
// MARK: Constants
fileprivate static let APPLE_LANGUAGE_KEY = "AppleLanguages"
/// Notification Name to observe when language change
static let ApplicationDidChangeLanguage = Notification.Name("ApplicationDidChangeLanguage")
// MARK: Properties
/// An array with all available languages as String
static var availableLanguages: [String]? = {
return UserDefaults.standard.object(forKey: APPLE_LANGUAGE_KEY) as? [String]
}()
/// The first element of available languages that is the current language
static var currentLanguageCode: String? = {
return availableLanguages?.first
}()
/// The current language code with just 2 characters
static var currentShortLanguageCode: String? = {
guard let currentLanguageCode = currentLanguageCode else {
return nil
}
let strIndex = currentLanguageCode.index(currentLanguageCode.startIndex, offsetBy: 2)
return currentLanguageCode.substring(to: strIndex)
}()
// MARK: Handle functions
/// This accepts the short language code or full language code
/// Setting this will send a notification with name "ApplicationDidChangeLanguage", that can be observed in order to refresh your localizable strings
class func setLanguage(withCode langCode: String) {
let matchedLangCode = availableLanguages?.filter {
$0.contains(langCode)
}.first
guard let fullLangCode = matchedLangCode else {
return
}
var reOrderedArray = availableLanguages?.filter {
$0.contains(langCode) == false
}
reOrderedArray?.insert(fullLangCode, at: 0)
guard let langArray = reOrderedArray else {
return
}
UserDefaults.standard.set(langArray, forKey: APPLE_LANGUAGE_KEY)
UserDefaults.standard.synchronize()
LocalizableLanguage.refreshAppBundle()
NotificationCenter.default.post(name: ApplicationDidChangeLanguage, object: fullLangCode)
}
}
// MARK: Refresh Bundle Helper
private extension LocalizableLanguage {
class func refreshAppBundle() {
MethodSwizzleGivenClassName(cls: Bundle.self, originalSelector: #selector(Bundle.localizedString(forKey:value:table:)), overrideSelector: #selector(Bundle.specialLocalizedStringForKey(_:value:table:)))
}
class func MethodSwizzleGivenClassName(cls: AnyClass, originalSelector: Selector, overrideSelector: Selector) {
let origMethod: Method = class_getInstanceMethod(cls, originalSelector);
let overrideMethod: Method = class_getInstanceMethod(cls, overrideSelector);
if (class_addMethod(cls, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(cls, overrideSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, overrideMethod);
}
}
}
extension Bundle {
func specialLocalizedStringForKey(_ key: String, value: String?, table tableName: String?) -> String {
let availableLanguages = UserDefaults.standard.object(forKey: LocalizableLanguage.APPLE_LANGUAGE_KEY) as? [String]
let currentLanguageCode = availableLanguages?.first ?? "en-US"
let currentShortLanguageCode = currentLanguageCode.substring(to: currentLanguageCode.index(currentLanguageCode.startIndex, offsetBy: 2))
let path =
Bundle.main.path(forResource: currentLanguageCode, ofType: "lproj") ??
Bundle.main.path(forResource: currentShortLanguageCode, ofType: "lproj") ??
Bundle.main.path(forResource: "Base", ofType: "lproj")
guard
self == Bundle.main,
let bundlePath = path,
let bundle = Bundle(path: bundlePath)
else {
return self.specialLocalizedStringForKey(key, value: value, table: tableName)
}
return bundle.specialLocalizedStringForKey(key, value: value, table: tableName)
}
}
You just need to copy that code and put in your project.
Then, you simple implement the listener like this:
NotificationCenter.default.addObserver(forName: LocalizableLanguage.ApplicationDidChangeLanguage, object: nil, queue: nil) { notification in
guard let langCode = notification.object as? String else {
return
}
self.accountStore.languageCode.value = langCode
}
Note that this line self.accountStore.languageCode.value = langCode is what i need to refresh when the app language as changed, then i can easily change all strings of my ViewModels in order to change the language to the user immediately.
In order to change the language, you can just call:
LocalizableLanguage.setLanguage(withCode: "en")
Other helper that could be nice to you is:
import Foundation
extension String {
var localized: String {
return NSLocalizedString(self, comment: "")
}
}
So if you have in your localizable files something like that:
main.view.title = "Title test";
You can simple call:
"main.view.title".localized
And you have your string translated.
According to Apple guidelines this is not a good idea to change language in the app programmatically, but in case u have no power to change requested behaviour, you can do something like next:
Prepare some service to manage your language even after app restart
enum LanguageName: String {
case undefined
case en
case es
}
let DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey = "DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey"
func dynamicLocalizableString(_ key: String) -> String {
return LanguageService.service.dynamicLocalizedString(key)
}
class LanguageService {
private struct Defaults {
static let keyAppleLanguage = "AppleLanguages"
static let keyCurrentLanguage = "KeyCurrentLanguage"
}
static let service:LanguageService = LanguageService()
var languageCode: String {
get {
return language.rawValue
}
}
var currentLanguage:LanguageName {
get {
var currentLanguage = UserDefaults.standard.object(forKey: Defaults.keyCurrentLanguage)
if let currentLanguage = currentLanguage as? String {
UserDefaults.standard.set([currentLanguage], forKey: Defaults.keyAppleLanguage)
UserDefaults.standard.synchronize()
} else {
if let languages = UserDefaults.standard.object(forKey: Defaults.keyAppleLanguage) as? [String] {
currentLanguage = languages.first
}
}
if let currentLanguage = currentLanguage as? String,
let lang = LanguageName(rawValue: currentLanguage) {
return lang
}
return LanguageName.undefined
}
}
func switchToLanguage(_ lang:LanguageName) {
language = lang
NotificationCenter.default.post(name: NSNotification.Name(rawValue: DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey), object: nil)
}
private var localeBundle:Bundle?
fileprivate var language: LanguageName = LanguageName.en {
didSet {
let currentLanguage = language.rawValue
UserDefaults.standard.set([currentLanguage], forKey:Defaults.keyAppleLanguage)
UserDefaults.standard.setValue(currentLanguage, forKey:Defaults.keyCurrentLanguage)
UserDefaults.standard.synchronize()
setLocaleWithLanguage(currentLanguage)
}
}
// MARK: - LifeCycle
private init() {
prepareDefaultLocaleBundle()
}
//MARK: - Private
fileprivate func dynamicLocalizedString(_ key: String) -> String {
var localizedString = key
if let bundle = localeBundle {
localizedString = NSLocalizedString(key, bundle: bundle, comment: "")
} else {
localizedString = NSLocalizedString(key, comment: "")
}
return localizedString
}
private func prepareDefaultLocaleBundle() {
var currentLanguage = UserDefaults.standard.object(forKey: Defaults.keyCurrentLanguage)
if let currentLanguage = currentLanguage as? String {
UserDefaults.standard.set([currentLanguage], forKey: Defaults.keyAppleLanguage)
UserDefaults.standard.synchronize()
} else {
if let languages = UserDefaults.standard.object(forKey: Defaults.keyAppleLanguage) as? [String] {
currentLanguage = languages.first
}
}
if let currentLanguage = currentLanguage as? String {
updateCurrentLanguageWithName(currentLanguage)
}
}
private func updateCurrentLanguageWithName(_ languageName: String) {
if let lang = LanguageName(rawValue: languageName) {
language = lang
}
}
private func setLocaleWithLanguage(_ selectedLanguage: String) {
if let pathSelected = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"),
let bundleSelected = Bundle(path: pathSelected) {
localeBundle = bundleSelected
} else if let pathDefault = Bundle.main.path(forResource: LanguageName.en.rawValue, ofType: "lproj"),
let bundleDefault = Bundle(path: pathDefault) {
localeBundle = bundleDefault
}
}
}
Add some rules to make sure you UI components will be always updated:
protocol Localizable {
func localizeUI()
}
Implement them
class LocalizableViewController: UIViewController {
// MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.localizeUI), name: NSNotification.Name(rawValue:DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey), object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
localizeUI()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
extension LocalizableViewController: Localizable {
// MARK: - Localizable
func localizeUI() {
fatalError("Must Override to provide inApp localization functionality")
}
}
Inherit any controller u want to conform dynamic app switch functionality and implement localizeUI() func
final class WelcomeTableViewController: LoadableTableViewController
Switch language as needed:
LanguageService.service.switchToLanguage(.en)
All localizabled string should be set as :
label.text = dynamicLocalizableString(<KEY_IN_STRINGS>)
Note: dont forget to add Localizable.strings with same codes as in LanguageName
With iOS 13 users can select App-specific language. if you really want to provide the facility to select language via app only then you can provide the facility to open settings within the app to select language.
https://developer.apple.com/videos/play/wwdc2019/403/

How do I use CaptiveNetwork to get the current WiFi Hotspot Name

I need to get the name of the currently connected Wi-Fi hotspot, e.g. "BT OpenZone"
I have been told it can be done with CaptiveNetwork specifically CNCopyCurrentNetworkInfo
My code so far:
#import <SystemConfiguration/CaptiveNetwork.h>
...
// Get the dictionary containing the captive network infomation
CFDictionaryRef captiveNtwrkDict = CNCopyCurrentNetworkInfo(kCNNetworkInfoKeySSID);
// Get the count of the key value pairs to test if it has worked
int count = CFDictionaryGetCount(captiveNtwrkDict);
NSLog(#"Count of dict:%d",count);
When the code runs on a device in a WiFi hotspot the captiveNtwrkDict is nil.
Has anyone managed to get it working? I cant find much documentation or any example code examples on CaptiveNetworks... any help would be much appreciated.
You need to find out which networks are available, and then pass them into CNCopyCurrentNetworkInfo. For example:
CFArrayRef myArray = CNCopySupportedInterfaces();
CFDictionaryRef myDict = CNCopyCurrentNetworkInfo(CFArrayGetValueAtIndex(myArray, 0));
...and you can then use the kCNNetworkInfoKeySSID on the dictionary you've got back (myDict) to find out the SSID. Don't forget to release/manage memory appropriately.
UPDATE FOR iOS 12, swift 4.2
iOS 12
You must enable Access WiFi Information from capabilities.
Important
To use this function in iOS 12 and later, enable the Access WiFi Information capability for your app in Xcode. When you enable this capability, Xcode automatically adds the Access WiFi Information entitlement to your entitlements file and App ID. Documentation link
Swift4.2
public class SSID {
class func fetchSSIDInfo() -> String {
var currentSSID = ""
if let interfaces = CNCopySupportedInterfaces() {
for i in 0..<CFArrayGetCount(interfaces) {
let interfaceName: UnsafeRawPointer = CFArrayGetValueAtIndex(interfaces, i)
let rec = unsafeBitCast(interfaceName, to: AnyObject.self)
let unsafeInterfaceData = CNCopyCurrentNetworkInfo("\(rec)" as CFString)
if let interfaceData = unsafeInterfaceData as? [String: AnyObject] {
currentSSID = interfaceData["SSID"] as! String
let BSSID = interfaceData["BSSID"] as! String
let SSIDDATA = interfaceData["SSIDDATA"]
debugPrint("ssid=\(currentSSID), BSSID=\(BSSID), SSIDDATA=\(SSIDDATA)")
}
}
}
return currentSSID
}
}
UPDATE FOR iOS 10
CNCopySupportedInterfaces is no longer deprecated in iOS 10. (API Reference)
You need to import SystemConfiguration/CaptiveNetwork.h and add SystemConfiguration.framework to your target's Linked Libraries (under build phases).
Here is a code snippet in swift (RikiRiocma's Answer):
import Foundation
import SystemConfiguration.CaptiveNetwork
public class SSID {
class func fetchSSIDInfo() -> String {
var currentSSID = ""
if 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
}
}
}
return currentSSID
}
}
(Important: CNCopySupportedInterfaces returns nil on simulator.)
For Objective-c, see Esad's answer here and below
+ (NSString *)GetCurrentWifiHotSpotName {
NSString *wifiName = nil;
NSArray *ifs = (__bridge_transfer id)CNCopySupportedInterfaces();
for (NSString *ifnam in ifs) {
NSDictionary *info = (__bridge_transfer id)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam);
if (info[#"SSID"]) {
wifiName = info[#"SSID"];
}
}
return wifiName;
}
UPDATE FOR iOS 9
As of iOS 9 Captive Network is deprecated*. (source)
*No longer deprecated in iOS 10, see above.
It's recommended you use NEHotspotHelper (source)
You will need to email apple at networkextension#apple.com and request entitlements. (source)
Sample Code (Not my code. See Pablo A's answer):
for(NEHotspotNetwork *hotspotNetwork in [NEHotspotHelper supportedNetworkInterfaces]) {
NSString *ssid = hotspotNetwork.SSID;
NSString *bssid = hotspotNetwork.BSSID;
BOOL secure = hotspotNetwork.secure;
BOOL autoJoined = hotspotNetwork.autoJoined;
double signalStrength = hotspotNetwork.signalStrength;
}
Side note: Yup, they deprecated CNCopySupportedInterfaces in iOS 9 and reversed their position in iOS 10. I spoke with an Apple networking engineer and the reversal came after so many people filed Radars and spoke out about the issue on the Apple Developer forums.
Easy to use code snippet(method):
Add SystemConfiguration.framework
import < SystemConfiguration/CaptiveNetwork.h>
use the below method
+ (NSString *)GetCurrentWifiHotSpotName {
NSString *wifiName = nil;
NSArray *ifs = (__bridge_transfer id)CNCopySupportedInterfaces();
for (NSString *ifnam in ifs) {
NSDictionary *info = (__bridge_transfer id)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam);
NSLog(#"info:%#",info);
if (info[#"SSID"]) {
wifiName = info[#"SSID"];
}
}
return wifiName;
}
Note that in Xcode 10 and iOS 12 you now need to enable the "Access Wifi Information" capability.
Source: https://openradar.appspot.com/43280182