How to change label text while changing the language in swift - ios

I am using two languages 1) english 2) French
In my project I need to change label language text without creating IBOutlet
I have created two string files called Localizable(English) and Localizable(French)
added text constants like this in project
code: I have created changeLanguage method and saved its language code in UserDefaults
static func changeLanguage(with : String) {
if with == "fr"{
Bundle.setLanguage("fr")
CommonUserDefaults.accessInstance.save("fr", forType: .languageCode)
}
else {
Bundle.setLanguage("en")
CommonUserDefaults.accessInstance.save("en", forType: .languageCode)
}
}
extension String {
var localizedWithLanguage : String{
NSLocalizedString(self, comment: "")
}
}
private var bundleKey: UInt8 = 0
class AnyLanguageBundle: Bundle {
override func localizedString(forKey key: String,
value: String?,
table tableName: String?) -> String {
guard let path = objc_getAssociatedObject(self, &bundleKey) as? String,
let bundle = Bundle(path: path) else {
return super.localizedString(forKey: key, value: value, table: tableName)
}
return bundle.localizedString(forKey: key, value: value, table: tableName)
}
}
extension Bundle {
class func setLanguage(_ language: String) {
defer {
object_setClass(Bundle.main, AnyLanguageBundle.self)
}
objc_setAssociatedObject(Bundle.main, &bundleKey, Bundle.main.path(forResource: language, ofType: "lproj"), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
and changing language like this in viewcontroller
class LoginVC: UIViewController {
#IBOutlet weak var emailTF: UITextField!
#IBOutlet weak var passwordTF: UITextField!
override func viewWillAppear(_ animated: Bool) {
Bundle.setLanguage("fr")
emailTF.placeholder = emailTF.placeholder?.localizedWithLanguage
passwordTF.placeholder = passwordTF.placeholder?.localizedWithLanguage
}
}
If I write emailTF.placeholder?.localizedWithLanguage like this then email and password placeholder text changing but i need to change static label text also but that is not changing why?
below o/p "Welcome back" and "Enter Your Email and Password" why these texts are not changed
how to change these label texts without crating IBOutlet, please guide me

Related

Swift property wrappers initilaization error

I am trying to use PropertWrapper for email validation. But when i try to initialize the eamilId varaible with empty string i am getting error : Incorrect argument label in call (have 'wrappedValue:', expected 'emailId:')
Here is the code of My view controller
class ViewController: UIViewController {
var name: String = ""
#EmailWrapper var emailId: String = ""
override func viewDidLoad() {
super.viewDidLoad()
name = "User Name"
emailId = "user123#gmail.com"
updateUserDetails()
}
func updateUserDetails() {
if name.isEmpty || emailId.isEmpty {
print("Please enter valid Name and emailId")
}
else {
print("User data updated successfully")
}
}
}
And the code for my property wrapper is
#propertyWrapper
struct EmailWrapper {
private var value = ""
var wrappedValue: String {
set {
value = newValue
}
get {
return isValidEmail(value) ? value : ""
}
}
init(emailId: String) {
self.value = emailId
}
private func isValidEmail(_ email: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailPred.evaluate(with: email)
}
}
How can I initialize the emailId with default value (String()) in ViewController while using the EmailWrapper
When you apply a property wrapper:
#EmailWrapper var emailId: String = ""
the Swift compiler actually generates a call to the initialiser to the initialiser of the property wrapper like this:
EmailWrapper(wrappedValue: "")
However, EmailWrapper doesn't have such an initialiser. Its only initialiser has the argument label emailId: instead.
To fix this, you just need to change the argument label of the initialiser:
init(wrappedValue: String) {
self.value = wrappedValue
}

App crashes at click with message: 'this class is not key value coding-compliant for the key postReplyButton.'

My app crashes when I click a cell in my tableView of recent posts. The click is supposed to segue me to the MainTextView which has the postReplyButton. The segue worked until I started experimenting with creating comments for the posts.
Here is the MainTextView code:
import Foundation
import UIKit
import Firebase
class MainTextView: UIViewController {
#IBOutlet weak var titleText: UILabel!
#IBOutlet weak var mainText: UILabel!
#IBOutlet weak var commentPlaceHolder: UILabel!
#IBOutlet weak var newCommentLabel: UITextView!
var delegate:NewPostVCDelegate?
#IBAction func postReplyButton() {
// Firebase code here
let postRef = Database.database().reference().child("posts").childByAutoId()
let postObject = [
"comment": newCommentLabel.text,
"timestamp": [".sv": "timestamp"]
] as [String : Any]
postRef.setValue(postObject, withCompletionBlock: { error, ref in
if error == nil {
self.delegate!.didUploadPost(withID: ref.key!)
self.dismiss(animated: true, completion: nil)
} else {
// Handle error
}
})
newCommentLabel.text = String()
commentPlaceHolder.isHidden = false
}
var post: Post?
// MARK: - View Controller LifeCycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setMain()
}
override func viewDidLoad() {
super.viewDidLoad()
newCommentLabel.delegate = self as! UITextViewDelegate
}
private func setMain() {
guard let post = self.post else {
return
}
titleText.text = post.text
mainText.text = post.title
}
func textViewDidChange(_commentView: UITextView) {
commentPlaceHolder.isHidden = !newCommentLabel.text.isEmpty
}
}
For reference, here is my Post class code:
import Foundation
class Post {
var id:String
var title: String
var text:String
var createdAt:Date
var comment: [String] = []
init(id: String, title: String,text:String, timestamp:Double, comment: [String] = []) {
self.id = id
self.title = title
self.text = text
self.createdAt = Date(timeIntervalSince1970: timestamp / 1000)
}
static func parse(_ key:String, data:[String:Any]) -> Post? {
if let title = data["text"] as? String,
let text = data["title"] as? String,
let timestamp = data["timestamp"] as? Double {
return Post(id: key, title: title, text: text, timestamp:timestamp, comment: [])
}
return nil
}
}
I suspect the issue may be with the delegate, which was declared as such in my NewPostViewController:
protocol NewPostVCDelegate {
func didUploadPost(withID id:String)
}
I have tried troubleshooting the storyboard, but everything seems to be in place. Is there an issue of the reuse of the protocol or perhaps the change of adding comments to the Post class itself? Maybe the issue is that I do not in fact want to upload a new post, but really I just want to add a comment to an existing post. If this is the case, how would I change the delegate or create a new one? I can provide more detail if needed. Thank you for your help.
This usually happens if you have an IBOutlet that was created previously with the same postReplyButton name. To check if your app has any other Outlet with the same name go to the Search section in your project and search for postReplyButton and see if you get multiple results for that name. If you do then click on the one which you don't need and delete it from the properties section.
If you have any Outlet which has a bad connection you will see something like this in the properties sections when you click on any one of the search result for postReplyButton
If that does not work then try renaming the Outlet entirely and see if that fixes the problem.
EDITED:
For your issue that you mentioned in the comments try this.
Instead of casting your newCommentLabel as an optional type of UITextViewDelegate just extend your viewController to conform to UITextViewDelegate. This should solve the issue.
class MainTextView: UIViewController, UITextViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
newCommentLabel.delegate = self
}
}
Once you add UITextViewDelegate to your viewController you will no longer get the warning in viewDidLoad to cast newCommentLabel as an optional of type UITextViewDelegate.

Adding Comment Replies to Posts as an Array of Strings in Class "Post"

I have created an app where users can generate posts that are added to a postTableView. Users can then click on any of the cells of postTableView to go to a unique view with the title and text of the post along with a commentTableView filled with user generated comments. Below the commentTableView is a textView that you can write your comment in and a button allowing you to submit your comment. I am trying to code my app so that when you press the button, the text that you wrote in the textView is appended to an array of unique comments for that post. Those comments populate the commentTableView. The following is my current flawed attempt:
Here is the Post Class:
import Foundation
class Post {
var id:String
var title: String
var text:String
var createdAt:Date
var comment: [String] = []
init(id: String, title: String,text:String, timestamp:Double, comment: [String] = []) {
self.id = id
self.title = title
self.text = text
self.createdAt = Date(timeIntervalSince1970: timestamp / 1000)
}
static func parse(_ key:String, data:[String:Any]) -> Post? {
if let title = data["text"] as? String,
let text = data["title"] as? String,
let timestamp = data["timestamp"] as? Double {
return Post(id: key, title: title, text: text, timestamp:timestamp, comment: [])
}
return nil
}
}
Here is my current view controller that you get when you click on any of the cells from the postTableView:
import Foundation
import UIKit
import Firebase
class MainTextView: UIViewController {
#IBOutlet weak var titleText: UILabel!
#IBOutlet weak var mainText: UILabel!
#IBOutlet weak var commentPlaceHolder: UILabel!
#IBOutlet weak var newCommentLabel: UITextView!
var delegate:NewPostVCDelegate?
#IBAction func postReplyButton() {
// Firebase code here
let postRef = Database.database().reference().child("posts").childByAutoId()
let postObject = [
"comment": newCommentLabel.text,
"timestamp": [".sv": "timestamp"]
] as [String : Any]
postRef.setValue(postObject, withCompletionBlock: { error, ref in
if error == nil {
self.delegate!.didUploadPost(withID: ref.key!)
self.dismiss(animated: true, completion: nil)
} else {
// Handle error
}
})
newCommentLabel.text = String()
commentPlaceHolder.isHidden = false
}
var post: Post?
// MARK: - View Controller LifeCycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setMain()
}
override func viewDidLoad() {
super.viewDidLoad()
newCommentLabel.delegate = self as! UITextViewDelegate
}
private func setMain() {
guard let post = self.post else {
return
}
titleText.text = post.text
mainText.text = post.title
}
func textViewDidChange(_commentView: UITextView) {
commentPlaceHolder.isHidden = !newCommentLabel.text.isEmpty
}
}
How can I fix my errors and programmatically execute my vision of populating my comment section with user for each post?
For
Class 'MainTextView' has no initializers
Replace
var delegate:NewPostVCDelegate
with
var delegate:NewPostVCDelegate?

Change app language using button programmatically

I'm developing an iOS app with five languages.I have done all the coding and now I'm trying to switch between languages using the button.But can't change the language..I found the problem but cant find the solution.
[NSBundle setLanguage:code];
Problem this line not calling..
Any help please? thanks..
Use localization to change the language of the application without restart.
Go to project -> select your project in the document outline -> Add the new language.
Add Localized.strings files to your project.
Add the strings which are used for localization in Localizable.strings(XXX)
For English "hello" = "Hello World";
For Hindi "hello" = "नमस्ते दुनिया";
Code:
import UIKit
extension String {
var localized: String {
let lang = currentLanguage()
let path = Bundle.main.path(forResource: lang, ofType: "lproj")
let bundle = Bundle(path: path!)
return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
}
//Remove here and add these in utilities class
func saveLanguage(_ lang: String) {
UserDefaults.standard.set(lang, forKey: "Locale")
UserDefaults.standard.synchronize()
}
func currentLanguage() -> String {
return UserDefaults.standard.string(forKey: "Locale") ?? ""
}
}
enum Language: String {
case english = "English"
case hindi = "हिंदी"
}
class ViewController: UIViewController {
var language = Language.english
override func viewDidLoad() {
super.viewDidLoad()
//Initial Setup
String().saveLanguage("en")
languageLabel.text = "hello".localized
languageButton.setTitle(language.rawValue, for: .normal)
}
func updateLanguage() {
if language == .english {
String().saveLanguage("hi")
language = .hindi
} else {
String().saveLanguage("en")
language = .english
}
languageLabel.text = "hello".localized
languageButton.setTitle(language.rawValue, for: .normal)
}
#IBOutlet weak var languageLabel: UILabel!
#IBOutlet weak var languageButton: UIButton!
#IBAction func changeLanguageButtonTapped(_ sender: UIButton) {
updateLanguage()
}
}

storyboard localization in swift 4.0

I want to implement storyboard localization in swift language. (means I want to localization for fix label and button text)
I have already idea about NSLocalizedString but I dont want to code for fix text Label
for e.g
NSLocalizedString("Welcome", comment: "")
I have already add Localizable.strings file as well as Main.string file for particular language. but I can not success in implement Localization
Follow below steps to localize Storyboard Elements:
Click on your project.
In Localizations section click on + icon and add language that you want to localize.
Once you add language you will see String file of that language.
Go to storyboard and click on UI Element that you want to localize.
Select identity inspector, In Document portion you will see Object ID that we need to use for localize that element.
Now goto localization file that created from step 3.
In that string file you will see Object ID of elements. Change value of that Object ID key it will reflect to that particular language only.
Once you have your localisation running, you can add extensions for UI elements, introducing easy localisation for them.
For UIlabel it will looks something like this:
public extension UILabel {
#IBInspectable public var localizedText: String? {
get {
return text
}
set {
text = NSLocalizedString(newValue ?? "", comment: "")
}
}
}
The #IBInspectable allows you setting the localisation key either from the storyboard and programatically too.
The storyboard localisation is the Apple provided way, but it bugs me a lot - not the most programmer-friendly for sure.
Bhumesh
I have used this library for in - app localisation. Which is very easy to use.
https://github.com/marmelroy/Localize-Swift
Now For Storyboard support I have Created Following extension that is IBDesignable So you can easily provide localised text from storyboard itself
1 ) Add This into new swift file
import Localize_Swift
#IBDesignable class LocalizableLabel: UILabel {
#IBInspectable var table :String? // Table
#IBInspectable var key:String? // KEY
#IBInspectable var extraTextToAppend:String? // Some text need to append , if any
override func awakeFromNib() {
guard let key = key else {return}
self.text = key.localized(using: table)
NotificationCenter.default.addObserver(self, selector: #selector(setText), name: NSNotification.Name(LCLLanguageChangeNotification), object: nil)
if let extraText = self.extraTextToAppend, let text = self.text {
self.text = text + extraText
}
}
#objc func setText () {
guard let key = key else {return}
self.text = key.localized(using: table)
if let extraText = self.extraTextToAppend, let text = self.text {
self.text = text + extraText
}
}
}
#IBDesignable class LocalizableButton: UIButton {
#IBInspectable var table :String?
#IBInspectable var key:String?
override func awakeFromNib() {
guard let key = key else {return}
self.setTitle(key.localized(using: table), for: .normal)
if let attributedText = self.attributedTitle(for: .normal) {
let mutableAttributedText = NSMutableAttributedString(attributedString: attributedText)
mutableAttributedText.replaceCharacters(in: NSMakeRange(0, mutableAttributedText.length), with: key.localized(using: table))
self.setAttributedTitle(mutableAttributedText, for: .normal)
}
NotificationCenter.default.addObserver(self, selector: #selector(setText), name: NSNotification.Name(LCLLanguageChangeNotification), object: nil)
}
#objc func setText () {
guard let key = key else {return}
self.setTitle(key.localized(using: table), for: .normal)
if let attributedText = self.attributedTitle(for: .normal) {
let mutableAttributedText = NSMutableAttributedString(attributedString: attributedText)
mutableAttributedText.replaceCharacters(in: NSMakeRange(0, mutableAttributedText.length), with: key.localized(using: table))
self.setAttributedTitle(mutableAttributedText, for: .normal)
}
}
}
#IBDesignable class LocalizeUINavigationItem: UINavigationItem {
#IBInspectable var table :String?
#IBInspectable var key:String?
override func awakeFromNib() {
guard let key = key else {return}
self.title = key.localized(using: table)
NotificationCenter.default.addObserver(self, selector: #selector(setText), name: NSNotification.Name(LCLLanguageChangeNotification), object: nil)
}
#objc func setText () {
guard let key = key else {return}
self.title = key.localized(using: table)
}
}
#IBDesignable class LocalizableUITextField: UITextField {
#IBInspectable var table_placeholder :String?
#IBInspectable var key_place_holder:String?
override func awakeFromNib() {
guard let key = key_place_holder else {return}
self.placeholder = key.localized(using: table_placeholder)
NotificationCenter.default.addObserver(self, selector: #selector(setText), name: NSNotification.Name(LCLLanguageChangeNotification), object: nil)
}
#objc func setText () {
guard let key = key_place_holder else {return}
self.placeholder = key.localized(using: table_placeholder)
}
}
2) Goto Storyboard set class to label and provide the key
3) Run and test
class ViewController: UIViewController {
#IBOutlet weak var resetOutlet: MyButton! {
didSet {
resetOutlet.setTitle("RESET".localized().uppercased(), for: .normal)
}
}
}
extension String {
func localized(tableName: String = "Localizable") -> String {
if let languageCode = Locale.current.languageCode, let preferredLanguagesFirst = Locale.preferredLanguages.first?.prefix(2) {
if languageCode != preferredLanguagesFirst {
if let path = Bundle.main.path(forResource: "en", ofType: "lproj") {
let bundle = Bundle.init(path: path)
return NSLocalizedString(self, tableName: tableName, bundle: bundle!, value: self, comment: "")
}
}
}
return NSLocalizedString(self, tableName: tableName, value: self, comment: "")
}
}

Resources