Changing language on the fly, in running iOS, programmatically - iphone

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/

Related

Select Text in Webkit applications via macOS accessibility API

I need to select text in a WebKit view of another application (Apple Mail) using accessibility APIs.
For regular text fields, I do something like this:
func selectText(withRange range: CFRange) throws {
var range = range
guard let newValue: AXValue = AXValueCreate(AXValueType.cfRange, &range) else { return }
AXUIElementSetAttributeValue(self, kAXSelectedTextRangeAttribute as CFString, newValue)
}
However, in the composing window of Apple Mail every text seems to be of type Static Text which doesn't come with the necessary AXSelectedTextRange
It has AXSelectedTextMarkerRange, though, which requires an AXTextMarker. I just don't get how to create one of these. I have no trouble reading the text from a user created selection using this here, but I'm unable to select text via the accessibility APIs.
Thanks to the hint from Willeke I was able to figure it out. It is indeed possible to do it using AXTextMarkerForIndex. Knowing that it's actually pretty straightforward.
Here's my code:
func getTextMarker(forIndex index: CFIndex) throws -> AXTextMarker? {
var textMarker: AnyObject?
guard AXUIElementCopyParameterizedAttributeValue(self,"AXTextMarkerForIndex" as CFString, index as AnyObject, &textMarker) == .success else { return nil }
return textMarker as! AXTextMarker
}
func selectStaticText(withRange range: CFRange) throws {
guard let textMarkerStart = try? getTextMarker(forIndex: range.location) else { return }
guard let textMarkerEnd = try? getTextMarker(forIndex: range.location + range.length) else { return }
let textMarkerRange = AXTextMarkerRangeCreate(kCFAllocatorDefault, textMarkerStart, textMarkerEnd)
AXUIElementSetAttributeValue(self, "AXSelectedTextMarkerRange" as CFString, textMarkerRange)
}

implementation of NSMetadataQuery along with UIDocuments in swiftUI

I am trying to make a document based app in swiftUI with a custom UI. I want iCloud capabilities in my app. I am trying to use iCloud Document (No cloudKit) way for storing data on iCloud container. I am using UIDocument and it's working. It's storing data to iCloud and I am able to retrieve it back.
Now the thing is when I run the app on two devices (iphone and iPad) and make changes to a file on one device, the changes are not reflecting on the other device while the file or say app is open. I have to close the app and relaunch it to see the changes.
I know I have to implement NSMetadataQuery to achieve this but I am struggling with it. I don't know any objective-C. I have been searching on the internet for a good article but could not find any. Can you please tell how do I implement this feature in my app. I have attach the working code of UIDocument and my Model class.
Thank you in advance !
UIDocument
class NoteDocument: UIDocument {
var notes = [Note]()
override func load(fromContents contents: Any, ofType typeName: String?) throws {
if let contents = contents as? Data {
if let arr = try? PropertyListDecoder().decode([Note].self, from: contents) {
self.notes = arr
return
}
}
//if we get here, there was some kind of problem
throw NSError(domain: "NoDataDomain", code: -1, userInfo: nil)
}
override func contents(forType typeName: String) throws -> Any {
if let data = try? PropertyListEncoder().encode(self.notes) {
return data
}
//if we get here, there was some kind of problem
throw NSError(domain: "NoDataDomain", code: -2, userInfo: nil)
}
}
Model
class Model: ObservableObject {
var document: NoteDocument?
var documentURL: URL?
init() {
let fm = FileManager.default
let driveURL = fm.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
documentURL = driveURL?.appendingPathComponent("savefile.txt")
document = NoteDocument(fileURL: documentURL!)
}
func loadData(viewModel: ViewModel) {
let fm = FileManager.default
if fm.fileExists(atPath: (documentURL?.path)!) {
document?.open(completionHandler: { (success: Bool) -> Void in
if success {
viewModel.notes = self.document?.notes ?? [Note]()
print("File load successfull")
} else {
print("File load failed")
}
})
} else {
document?.save(to: documentURL!, for: .forCreating, completionHandler: { (success: Bool) -> Void in
if success {
print("File create successfull")
} else {
print("File create failed")
}
})
}
}
func saveData(_ notes: [Note]) {
document!.notes = notes
document?.save(to: documentURL!, for: .forOverwriting, completionHandler: { (success: Bool) -> Void in
if success {
print("File save successfull")
} else {
print("File save failed")
}
})
}
func autoSave(_ notes: [Note]) {
document!.notes = notes
document?.updateChangeCount(.done)
}
}
Note
class Note: Identifiable, Codable {
var id = UUID()
var title = ""
var text = ""
}
This is a complex topic. Apple do provide some sample swift code, the Document-Based App Programming Guide for iOS and iCloud Design Guide.
There is also some good third party guidance: Mastering the iCloud Document Store.
I would recommend reading the above, and then return to the NSMetaDataQuery API. NSMetaDataQuery has an initial gathering phase and a live-update phase. The later phase can remain in operation for the lifetime of your app, allowing you to be notified of new documents in your app's iCloud container.

NSKeyedUnarchiver.unarchiveObject() unarchives old object

I want to save the user's filter selections on FilterViewController.
When FilterViewController is closed, NSKeyedArchiver.archiveRootObject archives the user's selections. However, NSKeyedUnarchiver.unarchiveObject opens up the initial default selections (NOT the user's selections). How to fix this?
FiltersViewController.swift
override func viewWillAppear(_ animated: Bool) {
if let filterSections = NSKeyedUnarchiver.unarchiveObject(withFile: filterViewModel.filtersFilePath) as? [FilterSection] {
// Retrieves initial default selections, NOT user's selection
filterViewModel.filterSections = filterSections
filtersTableView.reloadData()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Saves what user selects
let isSuccessful = NSKeyedArchiver.archiveRootObject(self.filterViewModel.filterSections, toFile: self.filterViewModel.filtersFilePath)
if (isSuccessful) {
print("Saved filters") // This is printed
} else {
print("Didn't Save filters")
}
}
FilterViewModel.swift
class FilterViewModel: NSObject {
// Contains all filtered sections displayed on table view
var filterSections: [FilterSection] = []
// File Path to saved Filter Sections
var filtersFilePath: String {
let manager = FileManager.default
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
print("this is the url path in the documentDirectory \(url)")
return (url!.appendingPathComponent("FilterSelectionData").path)
}
override init() {
super.init()
filterSections = self.getFilterSections()
}
}
CompanyViewController.swift
#objc func filterButtonTapped() {
var filterViewModel: FilterViewModel
if (self.filterViewModel != nil) {
filterViewModel = self.filterViewModel! // This runs
}
else {
self.filterViewModel = FilterViewModel()
filterViewModel = self.filterViewModel!
}
let filtersVC = FiltersViewController(filterViewModel: filterViewModel)
self.navigationController?.pushViewController(filtersVC, animated: true)
}
You are using self.getFilterSections to set filterSections in FilterViewModel init. I suppose self.getFilterSections is a method that returns the default values. For me, it should not be the case, rather if you have archived some values, you should get that in this method. Although, this alone should not be the reason for the issue, but may be a reason for inducing bug. Try changing self.getFilterSections to return archived values if possible otherwise default values and check whether the bug is still there.

How Save UILocalNotifications in CoreData

Answer is below, image is here:
I was searching how to do this for a couple of days and was only able to find people who stored UILocalNotificaations in NSUserDefaults. Saving these in NSUserDefaults seemed wrong to me because it is supposed to be used for small flags. I just now finally figured out how to store notifications in CoreData. This is Using Xcode 7.3.1 and Swift 2.2
First off you need to create a new entity in your CoreDataModel
and then add a single attribute to it. the attribute should be of type Binary Data I named my table/entity "ManagedFiredNotifications" and my attribute "notification". it should look like this:
Image linked in Question above.
Next you need to add an extension to UILocalNotification it should go like this:
extension UILocalNotification {
func save() -> Bool {
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
let firedNotificationEntity = NSEntityDescription.insertNewObjectForEntityForName("ManagedFiredNotifications", inManagedObjectContext: appDelegate!.managedObjectContext)
guard appDelegate != nil else {
return false
}
let data = NSKeyedArchiver.archivedDataWithRootObject(self)
firedNotificationEntity.setValue(data, forKey: "notification")
do {
try appDelegate!.managedObjectContext.save()
return true
} catch {
return false
}
}
}
Now for saving a notification all you need to do is call
UILocalNotification.save()
On the notification you would like to save. my notifications were named 'notification' so I would call notification.save()
To retrieve a notification you need a method like this
func getLocalFiredNotifications() -> [UILocalNotification]? {
let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)!.managedObjectContext
let firedNotificationFetchRequest = NSFetchRequest(entityName: "ManagedFiredNotifications")
firedNotificationFetchRequest.includesPendingChanges = false
do {
let fetchedFiredNotifications = try managedObjectContext.executeFetchRequest(firedNotificationFetchRequest)
guard fetchedFiredNotifications.count > 0 else {
return nil
}
var firedNotificationsToReturn = [UILocalNotification]()
for managedFiredNotification in fetchedFiredNotifications {
let notificationData = managedFiredNotification.valueForKey("notification") as! NSData
let notificationToAdd = NSKeyedUnarchiver.unarchiveObjectWithData(notificationData) as! UILocalNotification
firedNotificationsToReturn.append(notificationToAdd)
}
return firedNotificationsToReturn
} catch {
return nil
}
}
Note that this returns an array of UILocalNotifications.
When retrieving these if you plan on removing a few of them and then storing the list again you should remove them when you get them something like this works:
func loadFiredNotifications() {
let notifications = StudyHelper().getLocalFiredNotifications()
if notifications != nil {
firedNotifications = notifications!
} else {
// throw an error or log it
}
classThatRemoveMethodIsIn().removeFiredLocalNotifications()
}
I hope this helps someone who had the same problems that I did trying to implement this.

Swift: programmatically enumerate outgoing segues from a UIVIewController

I want to list the outgoing segues from a UIViewController, as described in Programmatically enumerate outgoing Segues for a UIViewController, but in Swift. (Swift 2, Xcode 7, iOS8+).
I can do
override func viewDidLoad() {
super.viewDidLoad()
let s = valueForKey("storyboardSegueTemplates")
print("switchingVC: segues: \(s)")
}
and that produces output like
switchingVC: segues: Optional((
"<UIStoryboardPresentationSegueTemplate: 0x1754a130>",
"<UIStoryboardPresentationSegueTemplate: 0x17534f60>",
"<UIStoryboardPresentationSegueTemplate: 0x17534fc0>"
))
but I struggle to produce anything after that. I can't find any definition of the UIStoryboardPresentationSegueTemplate. How can I persuade Swift to tell me what's inside it? How can I find the segue identifier?
Thanks!
this valueForKey("storyboardSegueTemplates") is UNDOCUMENTED property and UIStoryboardPresentationSegueTemplate is UNDOCUMENTED class. Beware of rejection from App Store if you are uploading application to App Store.
If you want to use this in your in-house projects, use as following
for template in (valueForKey("storyboardSegueTemplates") as? [AnyObject])! {
if let identifier = template.valueForKey("identifier") as? String {
print("identifier - " + identifier)
}
else {
print("no identifier for \(template)")
}
}
Found from https://github.com/JaviSoto/iOS9-Runtime-Headers/blob/master/Frameworks/UIKit.framework/UIStoryboardSegueTemplate.h
As per Swift 4.2 and from https://stackoverflow.com/a/35060917/1058199. Thanks /johnykutty.
import UIKit
extension UIViewController {
// Segue aids in Swift
#objc func isValidSegue(_ segueId: String?) -> Bool {
let filteredArray = (value(forKey: "storyboardSegueTemplates") as? NSArray)?.filtered(using: NSPredicate(format: "identifier = %#", segueId ?? ""))
let isValid = (filteredArray?.count ?? 0) > 0
return isValid
}
#objc func segues() -> Array<Any>? {
let segues = self.value(forKey: "storyboardSegueTemplates")
return segues as! Array<Any>?
}
#objc func segueNames() -> Array<AnyHashable> {
var segueNames = Array<Any>()
let filteredArray = (value(forKey: "storyboardSegueTemplates") as? NSArray)?.filtered(using: NSPredicate(format: "identifier != nil" ))
for template in filteredArray! as [AnyObject] {
if let identifier = (template.value(forKey: "identifier") as? String) {
segueNames.append(identifier)
}
else {
segueNames.append("no identifier for \(template)")
}
}
return segueNames as! Array<AnyHashable>
}
}
I know my use of predicates could be better, but Swift is such a PITA when dealing with iterating arrays. Please feel free to improve this.