So in the app I'm creating I have a settings view controller where users can type their information in the textfields and then when they click the done button it loads all that information to labels on the home view controller . How can I save the data in the home view controller with NSUserDefault? I'm using two view controllers.
Settings View Controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
let DestViewController = segue.destinationViewController as! home
DestViewController.NameText = NameTextField.text!
DestViewController.positionText = positionTextField.text!
DestViewController.divisionText = divisionTextField.text!
DestViewController.routeText = routeTextField.text!
DestViewController.routeText = routeTextField.text!
DestViewController._picture = _picture.image!
NSUserDefaults.standardUserDefaults().setValue(NameTextField.text!, forKey: "name")
NSUserDefaults.standardUserDefaults().setValue(positionTextField.text!, forKey: "position")
NSUserDefaults.standardUserDefaults().setValue(divisionTextField.text!, forKey: "division")
NSUserDefaults.standardUserDefaults().setValue(routeTextField.text!, forKey: "route")
NSUserDefaults.standardUserDefaults().setValue(_picture, forKey: "ImageKey")
}
Home View Controller
class home: UIViewController, UITextFieldDelegate {
#IBOutlet weak var Name: UILabel!
#IBOutlet weak var position: UILabel!
#IBOutlet weak var division: UILabel!
#IBOutlet weak var route: UILabel!
var contacts = MyContacts()
var NameText = String()
var positionText = String()
var divisionText = String()
var routeText = String()
var _picture: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
Name!.text = NSUserDefaults.standardUserDefaults().stringForKey("name")
position!.text = NSUserDefaults.standardUserDefaults().stringForKey("position")
division!.text = NSUserDefaults.standardUserDefaults().stringForKey("division")
route!.text = NSUserDefaults.standardUserDefaults().stringForKey("route")
Profile!.image = _picture
}
All of these codes work perfect, I'm just wondering if you could help me save the labels in NSUserDefault so every time the app is loaded all the information is still there. I've done a lot of research on this topic, but can find a solution for my problem. Any help would be much appreciated.
Just use basic syntax:
NSUserDefaults.standardUserDefaults().setValue(value, forKey:key)
Save example:-
NSUserDefaults.standardUserDefaults().setValue(positionTextField.text!, forKey: "position")
Retrieve example:-
position!.text=NSUserDefaults.standardUserDefaults().stringForKey("position")
Note: remember the key must be unique.
I have created following 2 functions which does Saving & Retrieving of Data using NSUserDefault. Refer it and use accordingly.
func saveDataInUserDefault()
{
// *** init NSUserDefaults ***
let userDefaults = NSUserDefaults()
// *** Init a UIImage, you can use your UIImage instead ***
let img = UIImage.init(imageLiteral: "image")
// *** set values in UserDefault as well image ***
userDefaults.setObject("value of name textfield", forKey: "name")
userDefaults.setObject("value of position textfield", forKey: "position")
userDefaults.setObject("value of division textfield", forKey: "division")
userDefaults.setObject("value of route textfield", forKey: "route")
// *** convert image to NSData and store it ***
userDefaults.setObject(UIImageJPEGRepresentation(img, 1.0), forKey: "image")
// *** save all the changes to NSUserDefaults ***
userDefaults.synchronize()
}
func getDataFromUserDefaults()
{
// *** Now lets access all the stored data from user default ***
// *** init NSUserDefaults ***
let userDefaults = NSUserDefaults()
// *** to get normal objects use following ***
print(userDefaults.objectForKey("name"))
// *** to load image use following ***
let img = UIImage(data:userDefaults.objectForKey("image") as! NSData)
}
Related
I have two xib file, one which shows login view and another which shows the steps what to do after the login is successful. I am having hard time to make it work. I have created macos project not ios and using safariservices so that it will work for the safari extension either.
Here is what i have done
import SafariServices
class SafariExtensionViewController: SFSafariExtensionViewController {
#IBOutlet weak var passwordMessage: NSTextField!
#IBOutlet weak var emailMessage: NSTextField!
#IBOutlet weak var message: NSTextField!
#IBOutlet weak var email: NSTextField!
#IBOutlet weak var password: NSSecureTextField!
static let shared = SafariExtensionViewController()
override func viewDidLoad() {
self.preferredContentSize = NSSize(width: 300, height: 250)
message.stringValue = ""
emailMessage.stringValue = ""
passwordMessage.stringValue = ""
}
override func viewDidAppear() {
if let storedEmail = UserDefaults.standard.object(forKey: "email") as? String {
if let stepView = Bundle.mainBundle.loadNibNamed(NSNib.Name(rawValue: "ExtensionStepsViewController"), owner: nil, topLevelObjects: nil)[0] {
self.view.addSubview(stepView)
}
}
}
#IBAction func userLogin(_ sender: Any) {
let providedEmailAddress = email.stringValue
let providedPassword = password.stringValue
let isEmailAddressValid = isValidEmailAddress(emailAddressString: providedEmailAddress)
self.message.stringValue = ""
emailMessage.stringValue = ""
passwordMessage.stringValue = ""
if isEmailAddressValid && providedPassword.count > 0 {
/* login process is handled here and store the email in local storage /*
/* TODO for now email is not stored in browser localstorage which has to be fixed */
let controller = "ExtensionStepsViewController"
let subview = ExtensionStepsViewController(nibName: NSNib.Name(rawValue: controller), bundle: nil)
self.view.addSubview(subview.view)
}
}
}
This way i get error like Type Bool has no subscript members my file structure looks something like this.
SafariExtensionViewController.xib (main one which is shown initially
with login screen)
SafariExtensionViewController.swift
ExtensionStepsViewController.xib(this view should be shown when user
is logged in instead of login screen)
ExtensionStepsViewController.swift
I am using xcode 10, swift 4, everything new.
UPDATE
I used the following block both in viewDidAppear(if there is email in localstorage then show extension steps view instead of login screen) and inside login function when the login is success but it does not navigate to that ExtensionStepsView
let controller = "ExtensionStepsViewController"
let subview = ExtensionStepsViewController(nibName: NSNib.Name(rawValue: controller), bundle: nil)
self.view.addSubview(subview.view)
Use case is show login at initial but if user is logged in then show another view but issue is now the view are merged
You got the error "Type Bool has no subscript members" because loadNibNamed(_:owner:topLevelObjects:) method of Bundle returns Bool struct that has no subscript members so you can't write like
true[0]
How to use this method correctly see the link and example from there:
var topLevelObjects : NSArray?
if Bundle.main.loadNibNamed(NSNib.Name(rawValue: "ExtensionStepsViewController"), owner: self, topLevelObjects: &topLevelObjects) {
let topLevelObjects!.first(where: { $0 is NSView }) as? NSView
}
Views were merged because you didn't remove previous views from the superview and added view from ExtensionStepsViewController to the same superview.
You can do the following steps to complete your issue:
Make SafariExtensionViewController inherited from SFSafariExtensionViewController that will be container (and parent) for two child view controllers such as LoginViewController and ExtensionStepsViewController and will be used to navigate between ones.
Make separately LoginViewController and ExtensionStepsViewController (both inherited from simple NSViewController) and its xibs.
Right after user logins transit from LoginViewController to ExtensionStepsViewController
As an example but instead of ParentViewController you have to use your implementation SafariExtensionViewController as I explain above in the first step.
public protocol LoginViewControllerDelegate: class {
func loginViewControllerDidLoginSuccessful(_ loginVC: LoginViewController)
}
public class LoginViewController: NSViewController {
weak var delegate: LoginViewControllerDelegate?
#IBAction func login(_ sender: Any) {
// login logic
let isLoginSuccessful = true
if isLoginSuccessful {
self.delegate?.loginViewControllerDidLoginSuccessful(self)
}
}
}
public class ExtensionStepsViewController: NSViewController {
}
public class ParentViewController: NSViewController, LoginViewControllerDelegate {
weak var login: LoginViewController! // using of force unwrap is anti-pattern. consider other solutions
weak var steps: ExtensionStepsViewController!
public override func viewDidLoad() {
let login = LoginViewController(nibName: NSNib.Name(rawValue: "LoginViewController"), bundle: nil)
login.delegate = self
// change login view frame if needed
login.view.frame = self.view.frame
self.view.addSubview(login.view)
// instead of setting login view frame you can add appropriate layout constraints
self.addChildViewController(login)
self.login = login
let steps = ExtensionStepsViewController(nibName: NSNib.Name(rawValue: "ExtensionStepsViewController"), bundle: nil)
steps.view.frame = self.view.frame
self.addChildViewController(steps)
self.steps = steps
}
// MARK: - LoginViewControllerDelegate
public func loginViewControllerDidLoginSuccessful(_ loginVC: LoginViewController) {
self.transition(from: self.login, to: self.steps, options: .slideLeft) {
// completion handler logic
print("transition is done successfully")
}
}
}
Here is a swift playground with this example.
UPD:
You can instantiate NSViewController in several ways:
Use NSStoryboard that allows to load view of NSViewController from .storyboard file:
let storyboard = NSStoryboard(name: NSStoryboard.Name("NameOfStoryboard"), bundle: nil)
let viewController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("NSViewControllerIdentifierInStoryboard"))
Use appropriate initialiser of NSViewController to load view of it from .xib file:
let steps = ExtensionStepsViewController(nibName: NSNib.Name(rawValue: "ExtensionStepsViewController"), bundle: nil)
Use default initialiser but you have to load view directly by overriding loadView() method if name of xib file is different from name of view controller class:
let steps = ExtensionStepsViewController()
// Also you have to override loadView() method of ExtensionStepsViewController.
I am a beginner student in Swift 3 and I am currently studying CoreData. I am trying to do an App where I have a first controller that is a list view (tableviewcontroller) where I can see some students. Inside each cell, I have an image (UIImage) and 4 labels (Strings: name, preceptor, note and date) who fetch data from an Array that keeps the information from the entity "AlunosLista", who has one attribute for each item (image is binary data). I can add these information through another view controller (AddDataVC.swift) and list them perfectly. The app until here is fine. What i cannot do, and i have been trying a lot of things, many things, is to send the data from the row selected (clicked) to another viewcontroller for the detailed view (DetailsVC.swift). When i was using a simple Array, without CoreData, worked fine. But now i cannot do it. Now parts of the code:
File (1): TableviewController
class TabelaListagem: UITableViewController {....
import CoreData
import UIKit
var alunos: [NSManagedObject?] = []
var gerenciadorDeDados: NSManagedObjectContext? = nil
override func viewDidLoad() {
super.viewDidLoad()
//CORE DATA
let AppleObject = UIApplication.shared.delegate as! AppDelegate
gerenciadorDeDados = AppleObject.persistentContainer.viewContext
LoadFetch()
}
(.......)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let path = alunos[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "celulaReuso", for: indexPath) as! ListagemCelulas
cell.lblAluno.text = path?.value(forKey: "nome") as? String
cell.lblPreceptor.text = path?.value(forKey: "preceptor") as? String
cell.lblData.text = path?.value(forKey: "dataHoje") as? String
cell.lblNotaAluno.text = path?.value(forKey: "nota") as? String
let caminhodaImagem = path?.value(forKey: "fotoAluno")
cell.imgAluno.image = UIImage(data: (caminhodaImagem as? NSData) as! Data)
return cell
}
Here should place the prepare(for segue), that I have tried many ways. This was the last one, who didn't worked too. "Build succeeded", but crashed.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueAluno" {
if let pathC = tableView.indexPathForSelectedRow{
let VCDestino = segue.destination as? DescriAluno
let objeto = FecthResultado?.object(at: pathC)
VCDestino?.alunoD = objeto as! NSManagedObject?
}
}
}
File (2) DetailViewController
import UIKit
import CoreData
class DescriAluno: UIViewController {
#IBOutlet weak var imgFotoAluno: UIImageView!
#IBOutlet weak var nomeAluno: UILabel
#IBOutlet weak var txtPreceptor: UILabel!
#IBOutlet weak var txtNotaAluno: UILabel!
#IBOutlet weak var txtDataHoje: UILabel!
var gerenciadorDeDados: NSManagedObjectContext!
var alunoD: NSManagedObject?
override func viewDidLoad() {
super.viewDidLoad()
//CORE DATA
let AppleObject = UIApplication.shared.delegate as! AppDelegate
gerenciadorDeDados = AppleObject.persistentContainer.viewContext
imgFotoAluno.image = alunoD?.value(forKey: "fotoAluno")
nomeAluno.text = alunoD?.value(forKey: "nome")
txtPreceptor.text = alunoD?.value(forKey: "preceptor")
txtNotaAluno.text = alunoD?.value(forKey: "nota")
txtDataHoje.text = alunoD?.value(forKey: "dataHoje")
}
Error message after crash:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: 'executeFetchRequest:error:
is not a valid NSFetchRequest.'
I really don't know how to proceed. I have tried so many things, some of them told about NSFetchedResults, but i could write or understand them. If any one could help here, I appreciate. Thank you.
This is the Fetch request (func):
Ok. This is my fetch request:
func LoadFecth() {
let ordenacaoAZ = NSSortDescriptor(key: "nome", ascending: true)
let ordenacaoAZPrecep = NSSortDescriptor(key: "preceptor", ascending: true)
let recupoerardados = NSFetchRequest<NSFetchRequestResult>(entityName: "AlunosLista")
recupoerardados.sortDescriptors = [ordenacaoAZPrecep, ordenacaoAZ]
do{
let recupera = try gerenciadorDeDados?.fetch(recupoerardados)
self.alunos = recupera as! [NSManagedObject]
self.tableView.reloadData()
}catch let erro as NSError{
print("Erro ao carregar: \(erro.description)")
}
}
Your core data life will get easier in every way if you use NSManagedObject subclasses for your entities. This allows you to use convenience accessors to get typed fetch requests and to skip all this value(forKey: stuff and use properties.
The error is that your fetch request is not valid.
You may have mistyped the entity name ("AlunosLista").
It may be getting upset because the type of the fetch request, without a specific NSManagedObject subclass, is actually NSFetchRequest<NSManagedObject>.
The best thing to do is enter a subclass for the entity in the model editor, generate the subclass files, then use this:
let recupoerardados: NSFetchRequest<AlunosLista> = AlunosLista.fetchRequest()
I am trying to self-learn OSX application development so I can make up all of my own bad habits 8).
Probably extraneous information
I have a trial app that works successfully - it resizes itself based on input from the user via a slider.
The key piece of code that does this is in one View controller ...
class JunkViewController2: NSViewController {
var myY: CGFloat!
#IBOutlet weak var mySlider: NSSlider!
#IBOutlet weak var myView: NSView!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
self.preferredContentSize = NSMakeSize(self.view.frame.width, 83)
}
#IBAction func mySlider(sender: NSSlider) {
let mySplitViewController = self.childViewControllers[0] as! JunkSplitViewController
switch mySlider.intValue {
case 3:
myY = 140.0
mySplitViewController.splitViewItems[2].collapsed = false
mySplitViewController.splitViewItems[1].collapsed = false
mySplitViewController.showSubview(2)
mySplitViewController.showSubview(1)
mySplitViewController.showSubview(0)
case 2:
myY = 110.0
mySplitViewController.splitViewItems[2].collapsed = true
mySplitViewController.splitViewItems[1].collapsed = false
mySplitViewController.hideSubview(2)
mySplitViewController.showSubview(1)
mySplitViewController.showSubview(0)
default:
myY = 80.0
mySplitViewController.splitViewItems[2].collapsed = true
mySplitViewController.splitViewItems[1].collapsed = true
mySplitViewController.hideSubview(2)
mySplitViewController.hideSubview(1)
mySplitViewController.showSubview(0)
}
mySplitViewController.preferredContentSize = NSMakeSize(self.view.frame.width, myY - 50 + 3)
self.preferredContentSize = NSMakeSize(self.view.frame.width, myY + 3)
}
}
More pertinent information
In what is working, above, on the story board I have three duplicate ViewControllers connected to a SplitView controller. I do a bunch of what feels like belts and suspenders work to make sure that everything gets resized properly - but the key part (I think) is the .collapsed property.
I am now trying to accomplish the same thing, using a completely different method - dynamically adding / removing split view items. This should allow me to have only one of the small ViewControllers on my story board, and then instantiate it as needed.
Following that idea, here is my SplitViewController ...
class JunkSplitViewController: NSSplitViewController {
#IBOutlet weak var mySplitView: NSSplitView!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
//mySplitView.adjustSubviews()
}
func makeChild() -> SmallViewController {
let mySmallGroup = NSStoryboard(name: "Main", bundle: nil).instantiateControllerWithIdentifier("smallVwCtl")
self.addSplitViewItem(mySmallGroup as! NSSplitViewItem)
return mySmallGroup as! SmallViewController
}
}
The main view controller invokes the makeChild function.
class JunkViewController: NSViewController {
#IBOutlet weak var mySlider: NSSlider!
#IBOutlet weak var myView: NSView!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
self.preferredContentSize = NSMakeSize(self.view.frame.width, 83)
}
#IBAction func mySlider(sender: NSSlider) {
let mySplitViewController = self.childViewControllers[0] as! JunkSplitViewController
while mySlider.intValue.toIntMax() > mySplitViewController.splitViewItems.count.toIntMax() {
mySplitViewController.makeChild()
}
while mySlider.intValue.toIntMax() < mySplitViewController.splitViewItems.count.toIntMax(){
mySplitViewController.splitViewItems.removeLast()
}
}
}
I get an error at the self.addSplitViewItem(mySmallGroup as! NSSplitViewItem) line of JunkSplitViewController ... "Could not cast value of type Scratch2.SmallViewController to NSSplitViewItem"
I've tried a handful of combinations (forcing mySmallGroup, 'self.addSplitViewItem(mySmallGroup as! SmallViewController)`, etc.) Everything leads to a similar error, either at compile or run time.
I cannot find any documentation on SplitViewItem.
So the question - what will work as input to addSplitViewItem and still successfully connect a new instance of SmallViewController?
And gratefully accept any comments/feedback on the methodology
I hate it when I find my answer minutes after posting a question ...
Based on info I found here ...
func makeChild() -> SmallViewController {
let mySmallGroup = NSStoryboard(name: "Main", bundle: nil).instantiateControllerWithIdentifier("smallVwCtl") as! SmallViewController
self.addSplitViewItem(NSSplitViewItem(viewController: mySmallGroup))
return mySmallGroup
}
... but I'd still like to hear any feedback on methodology. Thanks.
var score = 0
var highscore = 0
var defaults = NSUserDefaults.standardUserDefaults()
#IBOutlet weak var scoreResult: UILabel!
#IBOutlet weak var highScoreResult: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if let hscore = defaults.valueForKey("highscore") {
highScoreResult.text = String(hscore)
}
}
#IBAction func tapPressed(sender: AnyObject) {
score++
scoreResult.text = String(score)
if score > highscore {
highscore = score
highScoreResult.text = String(highscore)
defaults.setValue(highscore, forKey: "highscore")
defaults.synchronize()
}
}
When i press "tapPressed" button, score++ , then it saves as highscore, the problem is that when i restart app and i have highscore saved. When i press "tapPressed" highscore starts from zero again.
Change viewDidLoad to:
override func viewDidLoad() {
super.viewDidLoad()
if let hscore = defaults.valueForKey("highscore") {
highscore = hscore
highScoreResult.text = String(hscore)
}
}
That should load the highscore from the defaults once you're app launches.
In your viewDidLoad you need to change your statement to this:
if let hscore = defaults.valueForKey("highscore") {
highscore = hscore
highScoreResult.text = String(hscore)
}
The other answers are describing the reason for the error. In addition I'd like to describe also to use NSUserDefaults properly.
The recommended way is to register the key/value pairs to have reliable default values in case the preference file will be deleted or the app launches the first time.
In AppDelegate (applicationDidFinishLaunching or earlier) register highscore with a value of 0.
let defaultValues = ["highscore" : 0]
defaults.registerDefaults(defaultValues)
Then in viewDidLoad just write
let highscore = defaults.integerForKey("highscore")
highScoreResult.text = String(highscore)
There is no optional binding needed because the value in user defaults is always a non optional.
To save highscore back to disk use the dedicated method setInteger:forKey:
defaults.setInteger(highscore, forKey: "highscore")
valueForKey: is a KVC method with a special meaning and should not be used with NSUserDefaults.
I have a CoreData entity named 'Studio' with an attribute named 'name' with an NSManagedObject subclass created.
My app designed for a simple process, enter a name into a text box, and press 'save' and the name is saved into Studio.name - Press 'Update' and a text label is refreshed to show the value of Studio.name
However, it is not functioning as expected, if I, for example, enter the name 'Stack' and save the update I see 'Stack' in the text label, if i then enter 'Overflow' save/update the label reads 'Overflow', If i update it a third time to 'Swift' save/update the label again reads 'Stack'.
From there updates will give one of the three values seemingly at random.
Force quitting the app and relaunching it shows that the data is being saved to Core Data as pressing the update button will return a random previous value.
My question is, how does this happen with a string? (Shouldn't it only hold one value at a time?)
How can I correct this so it will only hold a single value and any subsequent values simply overwrite the previous value?
My code follows.
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var studioBox: UITextField!
#IBOutlet weak var nameLabel: UILabel!
#IBAction func saveData(sender: AnyObject) {
var studio = writeStudioData()
studio.name = studioBox.text
}
#IBAction func Update(sender: AnyObject) {
var studio = getStudioData()
nameLabel.text = studio.name
}
func getStudioData() -> Studio {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let request = NSFetchRequest(entityName: "Studio")
request.returnsObjectsAsFaults = false
let result = managedContext.executeFetchRequest(request, error: nil) as [Studio]
return result[0]
}
func writeStudioData () -> Studio {
let appDelegate =
UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let entityDescription = NSEntityDescription.entityForName("Studio", inManagedObjectContext: managedContext)
let result = Studio(entity: entityDescription!, insertIntoManagedObjectContext: managedContext)
return result
}
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.
}
}
You don't have one object, you are creating a new object every time. As you aren't including a sort descriptor with your fetch request, the order you get them, and thus the corresponding name, is unspecified, meaning it could be any of them.
You could either perform a fetch first in writeStudioData to see if there's already an object, only creating one if there isn't, or you could create one object and keep it around in a property.