I have one button, one text box and one button in my iOS app. The user inputs text, presses the button and the user input is shown in the label. Also, the user input is saved when the app is closed and ran again by UserDefaults. The code for the iOS app is as follows:
import UIKit
class ViewController: UIViewController {
// Connecting the front-end UI elements to the code
#IBOutlet weak var myTextField: UITextField!
#IBOutlet weak var myLabel: UILabel!
// Connecting the button to the code
#IBAction func myButton(_ sender: Any) {
UserDefaults.standard.set(myTextField.text, forKey: "myText")
myLabel.text = UserDefaults.standard.object(forKey: "myText") as? String
}
override func viewDidLoad() {
super.viewDidLoad()
// If there is any value stored previously, the stored data will be shown in the label and the text box to edit.
if (UserDefaults.standard.object(forKey: "myText") as? String) != nil {
UserDefaults.standard.set(myTextField.text, forKey: "myText")
myLabel.text = UserDefaults.standard.object(forKey: "myText") as? String
}
}
}
But now, I want to access the data stored in the key myText in the WatchKit Extension and display the text in the WatchKit App. I have inserted a label in the WatchKit App but want the WatchKit Extension to access the data stored in UserDefaults.standard.object(forKey: "myText").
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
// Connecting the label to the code
#IBOutlet var watchKitLabel: WKInterfaceLabel!
override func awake(withContext context: Any?) {
super.awake(withContext: context)
watchKitLabel.setText((UserDefaults.standard.object(forKey: "myText")) as? String)
}
}
Can anyone please help me? I have read the documentations about App Groups on various sources, but the documentation is either of Swift v3.1 or Objective-C. Or any other solution except App Groups will work. Also, I want the data to be platform-independent. So, if the user inputs once in the iOS app and quits, I want the data to be accessible by the watch right away.
Thanks in advance... :)
Related
Using UserDefaults to store two user input fields to be used throughout various VCs. UILabels show the current settings, while UITextFields accept updated input. I can set, store, display and update the two fields, but updates have to be in tandem. I cannot figure out the proper method to update only one of the two fields. As an example, update the email only, without touching "evernote" in my case. Without the work around below, and when only inputting one of two fields, the second non-input field is overridden as blank.
As a work around, I include the saved data as the initial input for the two UITextFields. Code works, just does not seem to be optimal. Ideally the two UITextFields would not have initial values. I have mucked around with != null statements in the button function, but to no avail.
Any advice would be greatly appreciated, and thank you in advance.
Class to share userData input among VCs:
class UDM {
static let shared = UDM()
let userData = UserDefaults(suiteName: "xxxx.saved.data")!
}
Outlets and Actions:
#IBOutlet weak var emailLabel: UILabel!
#IBOutlet weak var evernoteLabel: UILabel!
#IBOutlet weak var enterEmail: UITextField?
#IBOutlet weak var enterEvernote: UITextField?
#IBAction func saveButton(_ sender: UIButton) {
UDM.shared.userData.set(enterEmail?.text, forKey: "email")
UDM.shared.userData.set(enterEvernote?.text, forKey: "evernote")
}
viewDidLoad UI:
let emailObject = UDM.shared.userData.object(forKey: "email")
if let email = emailObject as? String {
enterEmail?.text = email
emailLabel.text = email
}
let evernoteObject = UDM.shared.userData.object(forKey: "evernote")
if let evernote = evernoteObject as? String {
enterEvernote?.text = evernote
evernoteLabel.text = evernote
}
Posting in case someone else is as dumb as I am. In the if/then statements for the UITextFields, I looked at enterEmail or enterEvernote as fields. Once I realized you need to look at the .text in the fields, it all fell into place. Updated action is pretty simple then:
#IBAction func saveButton(_ sender: UIButton) {
if enterEmail?.text != "" {
UDM.shared.userData.set(enterEmail?.text, forKey: "email")
}
if enterEvernote?.text != "" {
UDM.shared.userData.set(enterEvernote?.text, forKey: "evernote")
}
}
I'm trying to show the persons name in the today view extension; but I can't seem to do this. I've watched so many YouTube video's but they never helped. So if you can, I want you to answer this.
How the app works: You type your name inside the app. And the it will show in the today view extension.
About the app: I have a text field and button. The textfield is the persons name and the button is the save the name.
I want to show the name in the today extension.
Thank you.
Add the group to the entitlements/capabilities.
Go to the capabilities tab of the app's target
Enable App Groups
Create a new app group, entitled something appropriate. It must start with group..
Let Xcode go through the process of creating this group for you.
Save data to NSUserDefaults with group ID and use it in your extension.
From Apple's App Extension Guide :
https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html
In the main app, save person name:
let defaults = UserDefaults(suiteName: "your group ID")
defaults!.set("person name", forKey: "key for person name")
defaults!.synchronize()
In the extension, you can use saved person name:
let defaults = UserDefaults(suiteName: "your group ID")
let savedPersonName = defaults!.string(forKey: "key for person name")
Just a quick notice as I too have missed it
In order to get it working create the group on app target
and then on Today's target add Group Capability as well and tick the one just created from the main app's target (it should be listed)
here is a simple example of today extension in this example, I am only showing and updating the user name
this my today extension storyboard image
and today-view-Controller code is:
import UIKit
import NotificationCenter
class TodayViewController: UIViewController, NCWidgetProviding {
#IBOutlet weak var lnameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
if let name = UserDefaults.init(suiteName: "group.com.ahmad.widget")?.value(forKey: "name"){
lnameLabel.text = name as? String
}
else{
lnameLabel.text = "Wait..."
}
}
func widgetPerformUpdate(completionHandler: (#escaping (NCUpdateResult) -> Void)) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
completionHandler(NCUpdateResult.newData)
}
}
I create a storyboard and add a button in this than on his button action I update the user name on today extension
code of viewController is :
class ViewController: UIViewController {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var nameTextfield: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func SetNameOnWigetAction(_ sender: Any) {
nameLabel.text = nameTextfield.text
UserDefaults.init(suiteName: "group.com.ahmad.widget")?.setValue(nameTextfield.text, forKey: "name")
}
}
I have created a dummy IOS Application to explain my questions well. Let me share it with all details:
There are 2 Pages in this dummy IOS Application: LoginPageViewController.swift and HomepageViewController.swift
Storyboard id values are: LoginPage, Homepage.
There is login button in Login page.
There are 3 labels in Homepage.
App starts with Login page.
And i have a class file: UserDetail.swift
And there is one segue from login page to home page. Segue id is: LoginPage2Homepage
UserDetail.swift file
import Foundation
class UserDetail {
var accountIsDeleted = false
var userGUID : String?
var userAge: Int?
}
LoginPageViewController.swift file
import UIKit
class LoginPageViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func loginButtonPressed(_ sender: UIButton) {
var oUserDetail = UserDetail()
oUserDetail.accountIsDeleted = true
oUserDetail.userAge = 38
oUserDetail.userName = "Dirk Kuyt"
UserDefaults.standard.set(oUserDetail, forKey: "UserCredentialUserDefaults")
UserDefaults.standard.synchronize()
performSegue(withIdentifier: "LoginPage2Homepage", sender: nil)
}
}
HomepageViewController.swift file
import UIKit
class HomepageViewController: UIViewController {
var result_userGUID = ""
var result_userAge = 0
var result_isDeleted = false
#IBOutlet weak var labelUserGuidOutlet: UILabel!
#IBOutlet weak var labelAgeOutlet: UILabel!
#IBOutlet weak var labelAccountIsDeletedOutlet: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.setVariablesFromUserDefault()
labelUserGuidOutlet.text = result_userGUID
labelAgeOutlet.text = String(result_userAge)
labelAccountIsDeletedOutlet.text = String(result_isDeleted)
}
func setVariablesFromUserDefault()
{
if UserDefaults.standard.object(forKey: "UserCredentialUserDefaults") != nil
{
// I need a help in this scope
// I have checked already: My UserDefault exists or not.
// I need to check type of the value in UserDefault if UserDefault is exists. I need to show print if type of the value in UserDefault is not belongs to my custom class.
// And then i need to cast UserDefault to reach my custom class's properties: userGUID, userAge, isDeleted
}
else
{
print("there is no userDefault which is named UserCredentialUserDefaults")
}
}
}
My purposes:
I would like to store my custom class sample(oUserDetail) in UserDefaults in LoginPageViewController with login button click action.
I would like to check below in home page as a first task: My UserDefault exists or not ( I did it already)
I would like to check this in home page as a second task: if my UserDefault exists. And then check type of the UserDefault value. Is it created with my custom class? If it is not. print("value of userdefault is not created with your class")
Third task: If UserDefault is created with my custom class. And then parse that value. Set these 3 variables: result_userGUID, result_userAge, result_isDeleted to show them in labels.
I get an error after I click the login button in Login Page. Can't I store my custom class in UserDefaults? I need to be able to store because I see this detail while I am writing it:
UserDefaults.standart.set(value: Any?, forKey: String)
My custom class is in Any scope above. Isn't it?
You can't store a class instance without conforming to NSCoding / Codable protocols
class UserDetail : Codable {
var accountIsDeleted:Bool? // you can remove this as it's useless if the you read a nil content from user defaults that means no current account
var userGUID : String?
var userAge: Int?
}
store
do {
let res = try JSONEncoder().encode(yourClassInstance)
UserDefaults.standard.set(value:res,forKey: "somekey")
}
catch { print(error) }
retrieve
do {
if let data = UserDefaults.standard.data(forKey:"somekey") {
let res = try JSONDecoder().decode(UserDetail.self,from:data)
} else {
print("No account")
}
}
catch { print(error) }
This question already has answers here:
Unexpectedly found nil IBOutlet in prepareForSegue
(2 answers)
Closed 5 years ago.
I have a simple entity:
extension Team {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Team> {
return NSFetchRequest<Team>(entityName: "Team")
}
#NSManaged public var name: String?
#NSManaged public var phone: String?
#NSManaged public var department: String?
#NSManaged public var position: String?
}
Picture for easy understanding:
I load current entity from Core Data to App successfully. Furthermore, I have a UITableViewController with property members (it's storage of fetch result from Team entity).
var members: [Team] = []
After app launching (viewDidLoad(_:)) members.count is equal to 7 and that is right. Also, all these elements of members are using by UITableVIew in UITableViewController.
My task is open Detailed View Controller who will retrieve data from tapped cell. For this delegation I use classic prepare(for:sender:) method in UITableViewController (who is segue.source in current case):
// MARK: - Navigation
extension TeamListViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == segueIdentifier,
let memberDetailsViewController = segue.destination as? MemberDetailsViewController else {
fatalError("Wrong destination!")
}
guard let selectedIndex = tableView.indexPathForSelectedRow?.row else {
fatalError("Out of range")
}
guard let name = members[selectedIndex].name,
let phone = members[selectedIndex].phone,
let department = members[selectedIndex].department,
let position = members[selectedIndex].position else {
fatalError("No correct data")
}
memberDetailsViewController.memberNameLabel.text! = name
memberDetailsViewController.memberPhoneNumberLabel.text! = phone
memberDetailsViewController.memberDepartmentLabel.text! = department
memberDetailsViewController.memberPositionLabel.text! = position
}
}
So, App launches, I tap any cell whichever I want to open for more details and.. App crashes!
fatal error: unexpectedly found nil while unwrapping an Optional value
However, my variables (name, phone, department, position) are ok. They are with values!
Oh, yes. I try to retrieve a data for UILabels in MemberDetailsViewController below:
// MARK: - IBOutlets
#IBOutlet weak var memberNameLabel: UILabel!
#IBOutlet weak var memberPhoneNumberLabel: UILabel!
#IBOutlet weak var memberDepartmentLabel: UILabel!
#IBOutlet weak var memberPositionLabel: UILabel!
What is that can be a problem here?
Because of your are set your data in prepare(for segue: UIStoryboardSegue, sender: Any?) method in TeamListViewController where MemberDetailsViewController is not loaded properly thats way you got nil.
My suggestion is: Add Object like var members: Team? in MemberDetailsViewController and pass it from TeamListViewController so error will not appear.
I could get the data from the IOS,but watch. If I save data by watchkit extension, IOS couldn't get the data.So it's so strange. And I have added the Group and create the profile.
following is my Snippet:
class InterfaceController: WKInterfaceController {
#IBOutlet var outputLabel: WKInterfaceLabel!
override func awake(withContext context: Any?) {
super.awake(withContext: context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
// load the data from the IOS
#IBAction func loadData() {
let userDefaults = UserDefaults.standard
userDefaults.synchronize()
let outputData = userDefaults.string(forKey: "share")
self.outputLabel.setText(outputData)
}
}
class ViewController: UIViewController {
#IBOutlet weak var inputData: UITextField!
#IBOutlet weak var inputLabel: UILabel!
#IBOutlet weak var outputLabel: UILabel!
// input string into the fieldText and save them in Group by NSUserDefaults
#IBAction func saveData(_ sender: AnyObject) {
let inputData1 = self.inputData.text
self.inputLabel.text = inputData1
let userDefaults = UserDefaults.standard
userDefaults.setValue(inputData1, forKey: "share")
userDefaults.synchronize()
self.outputLabel.text = userDefaults.string(forKey: "share")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Even though an app extension bundle is nested within its containing app’s bundle, the running app extension and containing app have no direct access to each other’s containers.
To enable data sharing, use Xcode or the Developer portal to enable app groups for the containing app and its contained app extensions. Next, register the app group in the portal and specify the app group to use in the containing app.
After you enable app groups, an app extension and its containing app can both use the NSUserDefaults API to share access to user preferences. To enable this sharing, use the initWithSuiteName: method to instantiate a new NSUserDefaults object, passing in the identifier of the shared group
Objective C
// Create and share access to an NSUserDefaults object
NSUserDefaults * sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName: #"com.example.domain.MyShareExtension"];
// Use the shared user defaults object to update the user's account
[sharedDefaults setObject:#"Your Value" forKey:#"Your Key"];
swift:
func saveUserDefaults() {
let sharedDefaults = NSUserDefaults(suiteName: "com.example.domain.MyShareExtension")
sharedDefaults?.setObject("Your Value", forKey: "Your Key")
sharedDefaults?.synchronize()
}
If you want to create a shared userdefaults between devices you need to use the following:
GET
UserDefaults(suiteName: "GROUP NAME")!.object(forKey: "share")
SET
UserDefaults(suiteName: "GROUP NAME")!.set("Test", forKey: "share")
Where the GROUP NAME is the group name you did set when you created your app group.