We have a bug reported where a user has a device with an en language and nil region code, and thus all NSLocalizedString lookups in are failing, meaning our string key is what is rendered onscreen. Thus, if we had this in our en.lproj/Localizable.strings file:
"some_key" = "Some string.";
It would render some_key instead of Some string. in our UI.
First question: how do I replicate this scenario locally? This question on Stack seems to almost describe the issue, but does not describe how one enters this state.
Second question: why would iOS not fall back to English in the event the region code was nil?
Second question: why would iOS not fall back to English in the event
the region code was nil?
The cause can be "There is no base development language that is enabled". Or it is iOS logic.
Here is my solution for Localization. I just want to share with you an alternative solution if it can help you to solve the issue.
public enum AppResourceLang: String {
case en
case vi
}
public class AppResManager {
public static let shared = AppResManager()
public var lang = "en"
var textBundle: Bundle!
public var mainBundle: Bundle!
private init() {
let mainBundleId = Bundle.main.bundleIdentifier!
// we use mainBundle's bundleIdentifier because normally your running target will contains "lproj" in Copy Bundle Resource
// If your text/image is located on different project/target or framework, you need to enter that target's bundleId.
mainBundle = Bundle(identifier: mainBundle)!
let path = mainBundle.path(forResource: lang, ofType: "lproj")
textBundle = Bundle(path: path!)
}
public func changeLang(code: String) {
if let path = mainBundle.path(forResource: code, ofType: "lproj") {
lang = code; textBundle = Bundle(path: path)
} else {
// fallback to English
lang = "en"
let path = mainBundle.path(forResource: lang, ofType: "lproj")
textBundle = Bundle(path: path!)
}
}
}
Then we can use above textBundle like below:
public extension String {
var localText: String {
guard let bundle = AppResManager.shared.textBundle else { return self }
return NSLocalizedString(self, bundle: bundle, comment: "")
}
var lTextUpcased: String {
guard let bundle = AppResManager.shared.textBundle else { return self.uppercased() }
return NSLocalizedString(self, bundle: bundle, comment: "").uppercased()
}
}
Here is my AppResource (like a framework). We can see I have Localizable.strings and it is localized for EN, VI.
Here is the real file/folder structure, you can see if you check on English in Localization for *.string, *.storyboard,... file. It will be cloned and saved in this folder (ex: en.lproj). You can base on this to point to the corresponding resource file.
Then, how to use my above codes. It is just a way to allow you to completely control the Localization.
public enum AppLanguage: String {
case en
case vi
}
// MARK: - Device info
public static func getDeviceLang() -> AppLanguage {
guard let languageCode = Locale.current.languageCode else { return .en }
let appLang = AppLanguage(rawValue: languageCode.lowercased()) ?? .en
return appLang
}
// Then this will switch the language of your resource based on device language.
AppResManager.shared.changeLang(code: YourGlobalClass.getDeviceLang().rawValue)
// Then use the above string extension to load the corresponding text
// Anywhere in your project.
let text = "your_key".localText
Gmail client does not recognize line breaks within text from UIApplication.shared.openURL(url)
I have a function that returns a tuple of available email clients (validated by UIApplication.shared.canOpen) and the associated URL. It's for an error reporting feature, so the arguments array contains the text that will autopopulate email fields.
There are no issues with launching any of the three email clients, but gmail is the only one that doesn't process the line breaks. Does gmail use a different method?
enum EmailClient {
case gmail
case outlook
case mail
var title: String {
switch self {
case .gmail: return "Gmail"
case .outlook: return "Outlook"
case .mail: return "Mail"
}
}
//Creates url used by UIapplciation.shared to launch the client and autopopulate the email
func url(error: CustomError?) -> URL? {
guard let username = CredentialManager.username,
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
return nil
}
let arguments = [
username,
UIDevice().modelName,
UIDevice.current.systemVersion,
appVersion,
error?.description ?? "N/A" //When called from the settings page, no error is passed in
]
var urlFormat: String
switch self {
case .gmail: urlFormat = "googlegmail:///co?to=%#&subject=%#&body=%#"
case .outlook: urlFormat = "ms-outlook://compose?to=%#&subject=%#&body=%#"
case .mail: urlFormat = "mailto:%#?subject=%#&body=%#"
}
return URL(string: String(format: urlFormat, arguments: [
EMAIL_RECIPIENT,
EMAIL_SUBJECT.replacingOccurrences(of: " ", with: "%20"),
String(format: EMAIL_BODY_FORMAT, arguments: arguments).replacingOccurrences(of: " ", with: "%20").replacingOccurrences(of: "\n", with: "%0A")
]))
}
}
It seems that using "\r\n" instead of "\n" fixes the problem
1) Add the the scheme to your info.plist
We can do this through the beautiful thing that is the Info.plist file. Add a new key called LSApplicationQueriesSchemes as an array. Then you can enter your apps within the array. The Mail app doesn’t need to go in here, presumably because it is an Apple app. Your entry should look like the below
func openGmail(withFrom: String?, withSubject: String?) {
var gmailUrlString = "googlegmail:///"
if let from = withFrom {
gmailUrlString += "co?to=\(from)"
}
if let subject = withSubject {
gmailUrlString += "&subject=\(subject)"
}
}
One last thing we will need to do is URL encode the subject line before we pass it into the URL. We can do this by calling subjectString?.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed) on the string.
My requirement is to get domain name of URL by filtering out it's subdomain name.
i can get host name by using code as below
if let url = URL(string: "https://blog.abc.in/") {
if let hostName = url.host {
print("host name = \(hostName)") // output is: blog.mobilock.in
}
}
so here in URL blog is a subdomain and abc is a domain name, I wish to know/print only abc by excluding its subdomain parts.
In android, there is a class InternetDomainName which return domain name, the similar solution I am looking for iOS
I tried several answers and it's not duplicate of any or some of them is not working or that is a workaround.
Get the domain part of an URL string?
So finally i found better and standard approach for this issue -
Mozilla volunteers maintain Public Suffix List and there you can find list of library for respective language.
so in list Swift library is also present.
At the time of writing this answer Swift library don't have provison of adding it through CocoPods so you have to add downloaded project directly into your project. Code to get TLD name assuming Swift library is added into your project.
import DomainParser
static func getTLD(withSiteURL:String) -> String? {
do{
let domainParse = try DomainParser()
if let publicSuffixName = domainParse.parse(host: withSiteURL)?.publicSuffix {
if let domainName = domainParse.parse(host: withSiteURL)?.domain {
let tldName = domainName.replacingOccurrences(of: publicSuffixName, with: "").replacingOccurrences(of: ".", with: "")
print("top level name = \(tldName)")
return tldName
}
}
}catch{
}
return nil
}
Add Domain parser library as sub-project of your project, as pod of this library is not available yet
It is just a workaround but works perfectly:
if let url = URL(string: "https://x.y.z.a.b.blog.mobilock.in/") {
if let hostName = url.host {
print("host name = \(hostName)") // output is: x.y.z.a.b.blog.mobilock.in
let subStrings = hostName.components(separatedBy: ".")
var domainName = ""
let count = subStrings.count
if count > 2 {
domainName = subStrings[count - 2] + "." + subStrings[count - 1]
} else if count == 2 {
domainName = hostName
}
print(domainName)
}
}
Let me know if you face any issue.
There is no simple way, regardless of language. See How to extract top-level domain name (TLD) from URL for some good discussion of the difficulties involved.
To fetch the root domain of a URL, you can use the following URL extension:
extension URL {
var rootDomain: String? {
guard let hostName = self.host else { return nil }
let components = hostName.components(separatedBy: ".")
if components.count > 2 {
return components.suffix(2).joined(separator: ".")
} else {
return hostName
}
}
}
My app is supposed to support language change at runtime. I'm using SwiftGen 5.0. ViewControllers subscribe to language change notification and I've checked that the localisation function fires correctly. My overriden tr function looks like this:
fileprivate static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
guard let bundle = LanguageManager.shared.bundle else {
fatalError("Cannot find bundle!")
}
let format = NSLocalizedString(key, tableName: table, bundle: bundle, comment: "")
let locale = Locale(identifier: LanguageManager.shared.currentLanguageKey!)
return String(format: format, locale: locale, arguments: args)
}
The bundle is set like so:
if let path = Bundle.main.path(forResource: currentLanguageKey, ofType: "lproj") {
bundle = Bundle(path: path)
}
However, the tr function returns mostly previous language strings. Only one out of all labels currently in memory refreshes. Setting a breakpoint inside the function and printing bundle returns
NSBundle </var/containers/Bundle/Application/ED5A6C7D-1807-4319-8817-45E693BC45E2/MyApp.app/en_US.lproj> (not yet loaded)
which is the correct new language. After app restarts the language is set correctly. Am I doing something wrong?
Okay, I found the problem. The stencil was generating static variables:
static let label = L10n.tr("Localizable", "registration_verify.pin_code.label")
Changing stencil to generate computed properties fixed the behaviour:
static var label: String {
return L10n.tr("Localizable", "registration_verify.pin_code.label")
}
Now you can config lookupFunction params in swiftgen.yml file
strings:
inputs:
- en.lproj
outputs:
- templateName: structured-swift5
params:
lookupFunction: Localize_Swift_bridge(forKey:table:fallbackValue:)
output: L10n-Constants.swift
in your project you just need implement lookupFunction,
your can use use Localize_Swift library
import Localize_Swift;
func Localize_Swift_bridge(forKey:String,table:String,fallbackValue:String)->String {
return forKey.localized(using: table);
}
generated code may like this:
internal enum Localizable {
internal static var baseConfig: String { return
L10n.tr("Localizable", "base config", fallback: #"Base Config"#) }}
extension L10n {
private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String {
let format = Localize_Swift_bridge(forKey:table:fallbackValue:)(key, table, value)
return String(format: format, locale: Locale.current, arguments: args)
}
}
https://github.com/SwiftGen/SwiftGen/blob/stable/Documentation/templates/strings/structured-swift5.md
https://github.com/marmelroy/Localize-Swift
I would like to globally ignore all println() calls in my Swift code if I am not in a Debug build. I can't find any robust step by step instructions for this and would appreciate guidance. is there a way to do this globally, or do I need to surround every println() with #IF DEBUG/#ENDIF statements?
The simplest way is to put your own global function in front of Swift's println:
func println(object: Any) {
Swift.println(object)
}
When it's time to stop logging, just comment out the body of that function:
func println(object: Any) {
// Swift.println(object)
}
Or you can make it automatic by using a conditional:
func println(object: Any) {
#if DEBUG
Swift.println(object)
#endif
}
EDIT In Swift 2.0 println is changed to print. Unfortunately it now has a variadic first parameter; this is cool, but it means you can't easily override it because Swift has no "splat" operator so you can't pass a variadic in code (it can only be created literally). But you can make a reduced version that works if, as will usually be the case, you are printing just one value:
func print(items: Any..., separator: String = " ", terminator: String = "\n") {
Swift.print(items[0], separator:separator, terminator: terminator)
}
In Swift 3, you need to suppress the external label of the first parameter:
func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
Swift.print(items[0], separator:separator, terminator: terminator)
}
Updated for Swift 4.x:
With Swift 2.0/3.0 and Xcode 7/8 now out of beta, there have been some changes to how you disable the print function in release builds.
There are some important points mentioned by #matt and #Nate Birkholz above that are still valid.
The println() function has been replaced by print()
To use the #if DEBUG macro then you have to define the "Swift Compiler - Custom Flags -Other Flags" to contain the value -D DEBUG
I would recommend overriding the Swift.print() function in the global scope so that you can use the print() function as normal in your code, but it will remove output for non-debug builds. Here is a function signature that you can add at the global scope to do this in Swift 2.0/3.0:
func print(items: Any..., separator: String = " ", terminator: String = "\n") {
#if DEBUG
var idx = items.startIndex
let endIdx = items.endIndex
repeat {
Swift.print(items[idx], separator: separator, terminator: idx == (endIdx - 1) ? terminator : separator)
idx += 1
}
while idx < endIdx
#endif
}
Note: We have set the default separator to be a space here, and the default terminator to be a newline. You can configure this differently in your project if you would like.
Hope this helps.
Update:
It is usually preferable to put this function at the global scope, so that it sits in front of Swift's print function. I find that the best way to organize this is to add a utility file to your project (like DebugOptions.Swift) where you can place this function at the global scope.
As of Swift 3 the ++ operator will be deprecated. I have updated the snippet above to reflect this change.
The problem with all these approaches, including mine, is that they do not remove the overhead of evaluating the print arguments. No matter which of them you use, this is going to be expensive:
print(myExpensiveFunction())
The only decent solution is to wrap the actual print call in conditional compilation (let's assume that DEBUG is defined only for debug builds):
#if DEBUG
print(myExpensiveFunction())
#endif
That, and only that, prevents myExpensiveFunction from being called in a release build.
However, you can push back evaluation one level by using autoclosure. Thus, you could rewrite my solution (this is Swift 3) like this:
func print(_ item: #autoclosure () -> Any, separator: String = " ", terminator: String = "\n") {
#if DEBUG
Swift.print(item(), separator: separator, terminator: terminator)
#endif
}
This solves the problem just in the case where you are printing just one thing, which is usually true. That's because item() is not called in release mode. print(myExpensiveFunction()) thus ceases to be expensive, because the call is wrapped in a closure without being evaluated, and in release mode, it won't be evaluated at all.
Easy Answer - Xcode 14, Swift 5
Create a new file in your project and paste in this code:
import Foundation
func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
#if DEBUG
Swift.print(items, separator: separator, terminator: terminator)
#endif
}
This function signature matches the default Swift print so it overwrites the function throughout your project. If needed you can still access the original by using Swift.print().
Once you've added the code above, keep using print() the as usual and it will only print in debug builds.
As noted, i am a student and need things defined a little more clearly to follow along. After lots of research, the sequence I needed to follow is:
Click on the project name at the top of the File Navigator at the left of the Xcode project window. This is line that has the name of the project, how many build targets there are, and the iOS SDK version.
Choose the Build Settings tab and scroll down to the "Swift Compiler - Custom Flags" section near the bottom. Click the Down Arrow next to Other Flags to expand the section.
Click on the Debug line to select it. Place your mouse cursor over the right side of the line and double-click. A list view will appear. Click the + button at the lower left of the list view to add a value. A text field will become active.
In the text field, enter the text -D DEBUG and press Return to commit the line.
Add a new Swift file to your project. You are going to want to make a custom class for the file, so enter text along the lines of the following:
class Log {
var intFor : Int
init() {
intFor = 42
}
func DLog(message: String, function: String = __FUNCTION__) {
#if DEBUG
println("\(function): \(message)")
#endif
}
}
I was having trouble getting the class to be accepted by Xcode today, so the init may be a bit more heavyweight than necessary.
Now you will need to reference your custom class in any class in which you intend to use the new custom function in place of println() Add this as a property in every applicable class:
let logFor = Log()
Now you can replace any instances of println() with logFor.DLog(). The output also includes the name of the function in which the line was called.
Note that inside class functions I couldn't call the function unless I made a copy of the function as a class function in that class, and println() is also a bit more flexible with the input, so I couldn't use this in every instance in my code.
Here is a function that I use, which works perfectly in Swift 3:
func gLog<T>( _ object: #autoclosure() -> T, _ file: String = #file, _ function: String = #function, _ line: Int = #line)
{
#if DEBUG
let value = object()
let stringRepresentation: String
if let value = value as? CustomDebugStringConvertible
{
stringRepresentation = value.debugDescription
}
else if let value = value as? CustomStringConvertible
{
stringRepresentation = value.description
}
else
{
fatalError("gLog only works for values that conform to CustomDebugStringConvertible or CustomStringConvertible")
}
let fileURL = NSURL(string: file)?.lastPathComponent ?? "Unknown file"
let queue = Thread.isMainThread ? "UI" : "BG"
let gFormatter = DateFormatter()
gFormatter.dateFormat = "HH:mm:ss:SSS"
let timestamp = gFormatter.string(from: Date())
print("✅ \(timestamp) {\(queue)} \(fileURL) > \(function)[\(line)]: " + stringRepresentation + "\n")
#endif
}
Here is an example of the output it generates:
Explanation:
the green checkmark is used to enable you to quickly see your print (gLog) messages in the console, where they can sometimes get lost in a sea of other messages
the time/date stamp
the thread it is being run on -- in my case it is either the MainThread (which I call UI), or not the MainThread (which I call BG, for background thread)
the name of the file that the gLog message resides in
the function within the file that the gLog message resides in
the line number of the gLog message
the actual gLog message you would like to print out
Hope this is useful to someone else!
Tested with Swift 2.1 & Xcode 7.1.1
There's an easy way to exclude all print statements from release versions, once you know that empty functions are removed by the Swift compiler.
Side note : In the era of Objective-C, there was a pre-parser which could be used to remove NSLog statements before the compiler kicked in, like described in my answer here. But since Swift no longer has a pre-parser this approach is no longer valid.
Here's what I use today as an advanced and easily configurable log function, without ever having to worry about removing it in release builds. Also by setting different compiler flags, you can tweak the information that is logged as needed.
You can tweak the function as needed, any suggestion to improve it is welcome!
// Gobal log() function
//
// note that empty functions are removed by the Swift compiler -> use #if $endif to enclose all the code inside the log()
// these log() statements therefore do not need to be removed in the release build !
//
// to enable logging
//
// Project -> Build Settings -> Swift Compiler - Custom flags -> Other Swift flags -> Debug
// add one of these 3 possible combinations :
//
// -D kLOG_ENABLE
// -D kLOG_ENABLE -D kLOG_DETAILS
// -D kLOG_ENABLE -D kLOG_DETAILS -D kLOG_THREADS
//
// you can just call log() anywhere in the code, or add a message like log("hello")
//
func log(message: String = "", filePath: String = #file, line: Int = #line, function: String = #function) {
#if kLOG_ENABLE
#if kLOG_DETAILS
var threadName = ""
#if kLOG_THREADS
threadName = NSThread.currentThread().isMainThread ? "MAIN THREAD" : (NSThread.currentThread().name ?? "UNKNOWN THREAD")
threadName = "[" + threadName + "] "
#endif
let fileName = NSURL(fileURLWithPath: filePath).URLByDeletingPathExtension?.lastPathComponent ?? "???"
var msg = ""
if message != "" {
msg = " - \(message)"
}
NSLog("-- " + threadName + fileName + "(\(line))" + " -> " + function + msg)
#else
NSLog(message)
#endif
#endif
}
Here's where you set the compiler flags :
An example output with all flags on looks like this :
2016-01-13 23:48:38.026 FoodTracker[48735:4147607] -- [MAIN THREAD] ViewController(19) -> viewDidLoad() - hello
The code with the log() looks like this :
override func viewDidLoad() { log("hello")
super.viewDidLoad()
// Handle the text field's user input through delegate callbacks
nameTextField.delegate = self
}
Even simpler, after making sure -D DEBUG is set for the OTHER_SWIFT_FLAGS Debug build settings:
#if !DEBUG
func print(_ items: Any..., separator: String = " ", terminator: String = "\n") { }
#endif
XCode 8 introduced a few new build settings.
In particular one referred to Active Compilation Conditions does in a similar way what Other Flags settings did.
"Active Compilation Conditions" is a new build setting for passing conditional compilation flags to the Swift compiler.
As per XCode 8 (tested in 8.3.2) you will get this by default:
So without any config you can write the following:
#if DEBUG
print("⚠️ Something weird happened")
#endif
I strongly recommend you that if you use this approach extensively create a class/struct/function that wraps this logging logic. You may want to extend this further down the road.
Varun Naharia has the better solution so far. I would combine his answer with Rivera's ...
create a -D DEBUG flag on the compiler directives, build settings.
then add this code:
#if !DEBUG
public func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
}
#endif
This code will convert every print into nothing for release.
Swift 4
Xcode 10.0
maybe you could use this
func dPrint(_ message: #autoclosure () -> Any) {
#if DEBUG
print(message())
#endif
}
The reason of using #autoclosure is that if you pass a function as the message parameter, the function will be called only in debug mode, it will cause a performance hit.
unlike the Swift.print(_ items: Any..., separator: String = default, terminator: String = default) function, my solution has only one parameter, because in most cases, we don't pass multiple parameters as the print function only shows information in console, we can just convert the parameters to String: "\(param1)"+"\(param2)", right?
hope u like my solution
for my solution i make it simple
import UIKit
class DLog: NSObject {
init(title:String, log:Any) {
#if DEBUG
print(title, log)
#endif
}
}
then to show it just call
_ = DLog(title:"any title", log:Any)
You can also use a breakpoint, set it to continue after evaluation, and write the print message in the breakpoint!
You could define debug_println whose contents would be roughly:
#if DEBUG
println()
#endif
My Solution is use this code in AppDelegate before class
// Disable console log in live app
#if !arch(x86_64) && !arch(i386)
public func debugPrint(items: Any..., separator: String = " ", terminator: String = "\n") {
}
public func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
}
#endif
class AppDelegate: UIResponder, UIApplicationDelegate {
// App Delegate Code
}
Even simpler: take advantage of the fact that asserts are removed from release builds and only from there call the print. This removes all log calls (yes, even the calls to Log.da) as they are empty when building for release.
But I also heard that prints are removed for release builds, but not been able to find it in writing. So for now, I am using something like this Log below. I have a more meaty version on GitHub with emojis (for readability) and log topics (for consistency):
https://github.com/Gatada/JBits/blob/master/Project/Utility/Log.swift
public enum Log {
/// A date formatter used to create the timestamp in the log.
///
/// This formatter is only created if it is actually used, reducing the
/// overhead to zero.
static var formatter: DateFormatter?
// MARK: - API
/// Call to print message in debug area.
///
/// Asserts are removed in release builds, which make
/// the function body empty, which caused all calls to
/// be removed as well.
///
/// Result is zero overhead for release builds.
public static func da(_ message: String) {
assert(debugAreaPrint(message))
}
// MARK: - Helpers
/// The function that actually does the printing. It returns `true` to
/// prevent the assert from kicking in on debug builds.
private static func debugAreaPrint(_ message: String) -> Bool {
print("\(timestamp) - \(message)")
return true
}
/// Creates a timestamp used as part of the temporary logging in the debug area.
static private var timestamp: String {
if formatter == nil {
formatter = DateFormatter()
formatter!.dateFormat = "HH:mm:ss.SSS"
}
let date = Date()
return formatter!.string(from: date)
}
}
In code:
Log.da("This is only handled in a debug build.")
Seen in the Xcode debug area only when running a debug build:
13:36:15.047 - This is only handled in a debug build.
My Project was developed in Objective C, but from the past year I have started merging new code in Swift, So In Swift below solution worked for me, I have added that code in My Swift constant file :
func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
#if DEBUG
items.forEach {
Swift.print($0, separator: separator, terminator: terminator)
}
#endif
}
This works for me (add this as a global function in the project)
func print(_ items: Any...) {
#if DEBUG
Swift.print(items[0])
#endif
}