Creating a profile which can be used everywhere in the app - ios

I created an UIViewController which has two UITexFields and an UIImageView inside of it.
What I want it to be is a profile page, which provides information usable everywhere in the app.
What I tried to do is the following:
I created a Class with this code (based on Apple's tutorial on creating apps):
import UIKit
Class ProfilClass: NSObject, NSCoding {
//MARK: Properties
var bild: UIImage?
var vorname: String
var geburt: String
//MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("profil")
//MARK: Types
struct propKey {
static let bildKey = "bild"
static let vornameKey = "vorname"
static let geburtKey = "geburt"
}
//MARK: Initialization
init?(bild: UIImage?, vorname: String, geburt: String){
self.bild = bild
self.vorname = vorname
self.geburt = geburt
super.init()
if vorname.isEmpty || geburt.isEmpty {
return nil
}
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(bild, forKey: propKey.bildKey)
aCoder.encodeObject(vorname, forKey: propKey.vornameKey)
aCoder.encodeObject(geburt, forKey: propKey.geburtKey)
}
required convenience init?(coder aDecoder: NSCoder){
let bild = aDecoder.decodeObjectForKey(propKey.bildKey) as? UIImage
let vorname = aDecoder.decodeObjectForKey(propKey.vornameKey) as! String
let geburt = aDecoder.decodeObjectForKey(propKey.geburtKey) as! String
self.init(bild: bild, vorname: vorname, geburt: geburt)
}
}
I try to use this Class inside of my UIViewController:
override func viewDidLoad() {
[...]
if let profil = profil{
vornameLabel.text = profil.vorname
vornameField.text = profil.vorname
profilePic.image = profil.bild
geburtstagsLabel.text = profil.geburt
geburtstagField.text = profil.geburt
}
}
And when a save button is tapped:
#IBAction func butTap(sender: UIBarButtonItem) {
let vorname = vornameField.text
let geburt = geburtstagField.text
let bild = profilePic.image
profil = ProfilClass(bild: bild, vorname: vorname!, geburt: geburt!)
}
But after I close the UIViewController by going back to another one and reopen it, all the information is lost.
I don't know how to get the information again (I assume it is saved somewhere).
Can anyone help me?

public class MyViewState : NSObject{
static var isFromLogin = false
static var isFromCQ = false
}
add above to anywhere in any view controller it will accessible everywhere like
MyViewState.isFromLogin

make a shared class like following this will save data all the time
Class ProfilClass: NSObject, NSCoding {
static let sharedInstance = ProfilClass()
// here methods etc will go
}
and
ProfilClass.sharedInstance.(properties or method)
If you want data after app relaunched too , then save this to Userdefaults and load this class again and access anywhere in the app

Your mistake comes right at the end:
I assume it is saved somewhere
It is not, unless you ask for it to be saved. You need to use NSUserDefaults or NSKeyedArchiver to write your object. You've written all the code required to make that work, now you just need to do the reading and writing.
For example, to write your saved data you'll need something like this:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(profil, forKey: "SavedUserProfile")
I can't comment on reading because you have limited the code you posted:
[...]
if let profil = profil{
That is where your reading code should happen. I'm guessing(!) you're doing something like this:
let defaults = NSUserDefaults.standardUserDefaults()
if let profil = defaults.objectForKey("SavedUserProfile") as? ProfilClass
As you're following a specific Apple tutorial: You might find it easier just to continue following there rather than trying to use a different solution. Specifically, you need to continue onto the heading "Save and Load the Meal List", which is where the actual saving and loading happens. Apple writes the data to disk rather than user defaults, so you should follow along.
To be clear: the code you've written only enables your object to be saved and loaded. It doesn't actually do the saving.

var isFromLogin = false
var isFromCQ = false
Class ProfilClass: NSObject, NSCoding {.......}
add above to class it will accessible everywhere publicly . OR also create .swift file and declare constant variable to access globally

Related

Init method for a singleton

I have my file myAPI.swift, and two objet Round and GameStats. My Round object also have an attribute GameStats. So what I want to do is to get the GameStats property that I store inside my users defaults then assign it inside my Round object.
class myAPI: NSObject {
static let sharedInstance = myAPI()
var currentStats: GameStats?
var currentRound: Round?
private init(){
super.init()
self.loadData()
NSLog("Stats have been reload: \(self.currentRound?.gameStats)") // Return nil
// If I try to add this line the app stop running and nothing happens
NSLog("Test Singleton: \(myApp.sharedInstance.currentRound?.gameStats)")
}
func loadData(){
let backupNSData = NSUserDefaults.standardUserDefaults().objectForKey("backupNSData")
if let backupNSData = backupNSData as? NSData{
let backupData = NSKeyedUnarchiver.unarchiveObjectWithData(backupNSData)
if let backupData = backupData as? [String:AnyObject] {
guard let round = backupData["currentRound"] as? Round else {
print("error round loaddata")
return
}
self.currentRound = round
guard let stats = backupData["stats"] as? GameStats else {
print("error guard stats")
return
}
self.currentRound.gameStats = stats
NSLog("Stats reloaded: \(stats)") // This is not nil it works here
}
}
}
When my app crash I call this function to save the data
func backupData(){
var backupData:[String:AnyObject] = [String:AnyObject]()
if let round = self.currentRound {
backupData["currentRound"] = round
ColorLog.purple("Stats saved inside Round \(round.gameStats)")
}
if let stats = self.currentStat {
backupData["stats"] = stats
ColorLog.purple("Stats saved : \(stats)")
}
let backupNSData = NSKeyedArchiver.archivedDataWithRootObject(backupData)
NSUserDefaults.standardUserDefaults().setObject(backupNSData, forKey: "backupNSData")
NSUserDefaults.standardUserDefaults().synchronize()
}
So I have two question,
Is it normal that I can't call my singleton like myApp.sharedInstance.currentRound.id = 5 (for instance) inside the init() (I guess it is but I didn't find anything about that)
Why in my init() method my first NSLog self.currentRound?.gameStats is nil when in the function loadData() it wasn't ? It seems like it's losing its reference since we are leaving the function.
What am I doing right now is adding a currentStats property in my singleton, then when I retrieve data instead of doing self.currentRound.gameStats = stats I do self.currentStats = stats, then self.currentRoud.gameStats = self.currentStats and If I do that it works, I don't really know If I am doing the things right here.
Also my two objects Round and GameStats conform to NSCoding protocol as I implemented #objc func encodeWithCoder and #objc required init?(coder aDecoder: NSCoder) methods for both of them.
Thank for you help.

How to access a String from another class in swift?

I have two classes , one is called the ViewController which looks like this
import UIKit
class ViewController: UIViewController {
let demoURLs = DemoURLs
let ImgURL = NSURL(string: demoURLs.photoURL)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
The demoURLs.swift file looks like this
import Foundation
struct DemoURLs{
var photoURL = " xyz " }
But I get an error. I understand that my method is incorrect. What is the correct way of accessing this string , so that I can use it in an NSString? My final aim is to inflate an imageView with an image from this URL.
The way you are instantiating the struct is incorrect. Change
let demoURLs = DemoURLs
to
let demoURLs = DemoURLs() // the () calls init()
and you will be able to access the members of the struct.
struct DemoURLs {
static var photoURL = " xyz "
}
should do the job.
To read, from any class use:
let myString = DemoURLs.photoURL
To write:
DemoURLs.photoURL = "new name for xyz"

Realm Swift EXC_BAD_ACCESS During Segue with Unpersisted Object

I've got an app that I've started adding Realm to and I think I must be doing something wrong because I keep running into EXC_BAD_ACCESS when passing unpersisted objects between view controllers.
Here's a stripped down version of my app.
class TodoTask: Object {
dynamic var name: String = ""
let steps = List<Step>()
convenience init(name: String) {
self.init()
self.name = name
}
}
class Step: Object {
dynamic var name: String = ""
convenience init(name: String) {
self.init()
self.name = name
}
}
class TodoListController: UIViewController {
let todos = List<TodoTask>()
override func viewDidLoad() {
super.viewDidLoad()
var t1 = TodoTask(name: "Todo1")
let steps = [
Step("Do this"),
Step("Do that"),
]
t1.steps.appendContentsOf(steps)
var t2 = TodoTask(name: "Todo2")
let steps = [
Step("Do these"),
Step("Do those"),
]
t2.steps.appendContentsOf(steps)
todos.appendContentsOf([t1, t2])
// Display table of todos.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let detailsController = segue.destinationViewController as? TodoDetailViewController,
let selectedTask = getSelectedTask() {
detailsController.task = selectedTask
}
}
}
class TodoDetailViewController: UIViewController {
var task: TodoTask? // <<< EXC_BAD_ACCESS
// Display the task's steps.
}
Unfortunately, I can't figure out what triggers the EXC_BAD_ACCESS and it happens intermittently. I didn't copy a stacktrace (d'oh!) but I remember it being in the C++ destructor for some sort of Row object. This is odd to me because there doesn't seem to be a database file in my Documents folder.
I'm pretty confident this is a Realm-based error because I experienced no weird crashes until I converted my plain Swift objects to Realm objects.
My only hunch is that I might be using List wrong since the warning This method can only be called during a write transaction. is in the comments of the appendContentsOf method. However, I was under the impression that I could use Realm objects that weren't stored in a Realm just like normal Swift objects.
Am I using Realm objects wrong? Is there anything else I can look into?
I'm on Realm 0.95.2.
Thanks!
Edit:
func getSelectedTask() -> TodoTask? {
if let index = taskTableView.indexPathForSelectedRow {
return tasks[index]
}
return nil
}
The issue is that a Realm List should not be created directly. This class is only used to manage to-many relationships on Object models. Instead, you should use a standard Swift array to store the unpersisted Realm Objects.
So switch:
let todos = List<TodoTask>()
to
let todos = [TodoTask]()

How to store a custom array of objects using NSUserDefaults Swift

I am a novice programmer who has started to learn Swift to make apps.
So my question is: "How can I store an array of my objects using NSUserDefaults". Originally, I was looking at this question posted on stack overflow. However, I don't really understand exactly how they did it. After searching on youtube with really no success, I have no choice but ask a question on stack overflow. (I'm hesitant because people often vote my questions off as I'm a beginner asking stupid questions)
So here goes.
I am making a todo list using swift. I have one main class called Task Manager that contains an array of Task objects.
Task Manager (Functionality)
Contains the array of tasks
Saves and Loads data using NSUserDefaults in order to store the tasks
Task (Functionality)
Stores the name and description of a task
Not sure if it should have anything else
So now, after looking at several online tutorials, here is what i have. (It doesn't work)
Task Manager: Raw Code
//
// TaskManager.swift
// ToDoList
//
// Created by Ronak Shah on 7/5/15.
// Copyright (c) 2015 com.ShahIndustries. All rights reserved.
//
import UIKit
var taskMgr : TaskManager = TaskManager()
class TaskManager: NSObject{
var tasks: [Task]? = [Task]()
var time = 1 //debug, ignore
override init(){
super.init()
let taskData = NSUserDefaults.standardUserDefaults().objectForKey("tasks") as? NSData
if let taskData = taskData {
let taskArray = NSKeyedUnarchiver.unarchiveObjectWithData(taskData) as? [Task]
if let taskArray = taskArray {
tasks = taskArray
}
}
}
func addTask(taskName : String, taskDescription : String){
var newTask = Task(taskName: taskName , taskDescription: taskDescription)
tasks!.append(newTask)
}
func getTaskAtIndex(index: Int) ->Task {
return tasks![index]
}
func saveData() {
let ud = NSUserDefaults.standardUserDefaults()
ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(taskMgr), forKey: "tasks")
}
required init(coder aDecoder: NSCoder) {
if let dataTasks = aDecoder.decodeObjectForKey("tasks") as? [Task] {
self.tasks = dataTasks
}
}
func encodeWithCoder(aCoder: NSCoder) {
if let tasks = self.tasks {
aCoder.encodeObject(tasks, forKey: "tasks")
}
}
func loadData() {
let ud = NSUserDefaults.standardUserDefaults()
if let data = ud.objectForKey("tasks") as? NSData {
//not sure how to get the data
}
}
}
Task: Raw Code
//
// Task.swift
// ToDoList
//
// Created by Ronak Shah on 7/5/15.
// Copyright (c) 2015 com.ShahIndustries. All rights reserved.
//
import Foundation
class Task :NSObject{
var name : String?
var desc : String?
init (taskName : String, taskDescription : String){
self.name = taskName
self.desc = taskDescription
}
required init(coder aDecoder: NSCoder) {
if let dataDesc = aDecoder.decodeObjectForKey("desc") as? String {
self.desc = dataDesc
}
if let dataTitle = aDecoder.decodeObjectForKey("name") as? String {
self.name = dataTitle
}
}
func encodeWithCoder(aCoder: NSCoder) {
if let name = self.name {
aCoder.encodeObject(name, forKey: "name")
}
if let desc = self.desc {
aCoder.encodeObject(desc, forKey: "desc")
}
}
}
So how might I store my array of tasks (named tasks in Task Manager)?
The docs say:
For NSArray and NSDictionary objects, their contents must be property list objects
There's NSData among the property list types. In order to convert you custom object to NSData you have to serialize it.
You are trying to implement the archiving step on Task and TaskManager objects which adds complexity. The easiest way to achieve your goal is to archive (convert to NSData) just Task objects and add them to an array before saving to the defaults. In order to do that you have to convert the tasks array of type [Task] to an array of type [NSData] It would look like this.
func saveData() {
let tasksAsData = tasks.map { task in
return NSKeyedArchiver.archivedDataWithRootObject(task)
}
let ud = NSUserDefaults.standardUserDefaults()
ud.setObject(tasksAsData, forKey: "tasks")
}
When you get the data back you have to convert NSData objects into Tasks. You can do it in the following way.
func loadData() {
let ud = NSUserDefaults.standardUserDefaults()
if let data = ud.objectForKey("tasks") as? [NSData] {
tasks = data.map { data in
return NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
}
}
There you go! I think you can take it from here.
As a side note I am advising not to use NSUserDefaults for such purpose. The defaults are meant as a simple way of saving little pieces of (usually) unstructured data. When you add more properties to your tasks it is likely to get cumbersome (you'll probably serialize things like image, date, or location). It is not space efficient nor is it fast. I advise to take a look at other ways to persist data on iOS like SQLite, Core Data, Realm or many others.

How to archive and unarchive custom objects in Swift? Or how to save custom object to NSUserDefaults in Swift?

I have a class
class Player {
var name = ""
func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(name)
}
func initWithCoder(decoder: NSCoder) -> Player {
self.name = decoder.decodeObjectForKey("name") as String
return self
}
init(coder aDecoder: NSCoder!) {
self.name = aDecoder.decodeObjectForKey("name") as String
}
init(name: String) {
self.name = name
}
}
and i want to serialise it and save to user defaults.
First of all I'm not sure how to correctly write encoder and decoder. So for init i wrote two methods.
When i try to execute this code:
func saveUserData() {
let player1 = Player(name: "player1")
let myEncodedObject = NSKeyedArchiver.archivedDataWithRootObject(player1)
NSUserDefaults.standardUserDefaults().setObject(myEncodedObject, forKey: "player")
}
app crashes and i get this message:
*** NSForwarding: warning: object 0xebf0000 of class '_TtC6GameOn6Player' does not implement methodSignatureForSelector: -- trouble ahead
What do i do wrong?
In Swift 4 you don't need NSCoding anymore! There is a new protocol called Codable!
class Player: Codable {
var name = ""
init(name: String) {
self.name = name
}
}
And Codable also supports Enums and Structs so you can rewrite your player class to a struct if you want!
struct Player: Codable {
let name: String
}
To save your player in Userdefaults:
let player = Player(name: "PlayerOne")
try? UserDefaults.standard.set(PropertyListEncoder().encode(player), forKey: "player")
Note: PropertyListEncoder() is a class from the framework Foundation
To Retrieve:
let encoded = UserDefault.standard.object(forKey: "player") as! Data
let storedPlayer = try! PropertyListDecoder().decode(Player.self, from: encoded)
For more information, read https://developer.apple.com/documentation/swift/codable
NSKeyedArchiver will only work with Objective-C classes, not pure Swift classes. You can bridge your class to Objective-C by marking it with the #objc attribute or by inheriting from an Objective-C class such as NSObject.
See Using Swift with Cocoa and Objective-C for more information.
tested with XCode 7.1.1, Swift 2.1 & iOS 9
You have a few options to save your (array of) custom objects :
NSUserDefaults : to store app settings, preferences, user defaults :-)
NSKeyedArchiver : for general data storage
Core data : for more complex data storage (database like)
I leave Core data out of this discussion, but want to show you why you should better use NSKeyedArchiver over NSUserdefaults.
I've updated your Player class and provided methods for both options. Although both options work, if you compare the 'load & save' methods you'll see that NSKeydArchiver requires less code to handle arrays of custom objects. Also with NSKeyedArchiver you can easily store things into separate files, rather than needing to worry about unique 'key' names for each property.
import UIKit
import Foundation
// a custom class like the one that you want to archive needs to conform to NSCoding, so it can encode and decode itself and its properties when it's asked for by the archiver (NSKeydedArchiver or NSUserDefaults)
// because of that, the class also needs to subclass NSObject
class Player: NSObject, NSCoding {
var name: String = ""
// designated initializer
init(name: String) {
print("designated initializer")
self.name = name
super.init()
}
// MARK: - Conform to NSCoding
func encodeWithCoder(aCoder: NSCoder) {
print("encodeWithCoder")
aCoder.encodeObject(name, forKey: "name")
}
// since we inherit from NSObject, we're not a final class -> therefore this initializer must be declared as 'required'
// it also must be declared as a 'convenience' initializer, because we still have a designated initializer as well
required convenience init?(coder aDecoder: NSCoder) {
print("decodeWithCoder")
guard let unarchivedName = aDecoder.decodeObjectForKey("name") as? String
else {
return nil
}
// now (we must) call the designated initializer
self.init(name: unarchivedName)
}
// MARK: - Archiving & Unarchiving using NSUserDefaults
class func savePlayersToUserDefaults(players: [Player]) {
// first we need to convert our array of custom Player objects to a NSData blob, as NSUserDefaults cannot handle arrays of custom objects. It is limited to NSString, NSNumber, NSDate, NSArray, NSData. There are also some convenience methods like setBool, setInteger, ... but of course no convenience method for a custom object
// note that NSKeyedArchiver will iterate over the 'players' array. So 'encodeWithCoder' will be called for each object in the array (see the print statements)
let dataBlob = NSKeyedArchiver.archivedDataWithRootObject(players)
// now we store the NSData blob in the user defaults
NSUserDefaults.standardUserDefaults().setObject(dataBlob, forKey: "PlayersInUserDefaults")
// make sure we save/sync before loading again
NSUserDefaults.standardUserDefaults().synchronize()
}
class func loadPlayersFromUserDefaults() -> [Player]? {
// now do everything in reverse :
//
// - first get the NSData blob back from the user defaults.
// - then try to convert it to an NSData blob (this is the 'as? NSData' part in the first guard statement)
// - then use the NSKeydedUnarchiver to decode each custom object in the NSData array. This again will generate a call to 'init?(coder aDecoder)' for each element in the array
// - and when that succeeded try to convert this [NSData] array to an [Player]
guard let decodedNSDataBlob = NSUserDefaults.standardUserDefaults().objectForKey("PlayersInUserDefaults") as? NSData,
let loadedPlayersFromUserDefault = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSDataBlob) as? [Player]
else {
return nil
}
return loadedPlayersFromUserDefault
}
// MARK: - Archivig & Unarchiving using a regular file (using NSKeyedUnarchiver)
private class func getFileURL() -> NSURL {
// construct a URL for a file named 'Players' in the DocumentDirectory
let documentsDirectory = NSFileManager().URLsForDirectory((.DocumentDirectory), inDomains: .UserDomainMask).first!
let archiveURL = documentsDirectory.URLByAppendingPathComponent("Players")
return archiveURL
}
class func savePlayersToDisk(players: [Player]) {
let success = NSKeyedArchiver.archiveRootObject(players, toFile: Player.getFileURL().path!)
if !success {
print("failed to save") // you could return the error here to the caller
}
}
class func loadPlayersFromDisk() -> [Player]? {
return NSKeyedUnarchiver.unarchiveObjectWithFile(Player.getFileURL().path!) as? [Player]
}
}
I've tested this class as follows (single view app, in the viewDidLoad method of the ViewController)
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// create some data
let player1 = Player(name: "John")
let player2 = Player(name: "Patrick")
let playersArray = [player1, player2]
print("--- NSUserDefaults demo ---")
Player.savePlayersToUserDefaults(playersArray)
if let retreivedPlayers = Player.loadPlayersFromUserDefaults() {
print("loaded \(retreivedPlayers.count) players from NSUserDefaults")
print("\(retreivedPlayers[0].name)")
print("\(retreivedPlayers[1].name)")
} else {
print("failed")
}
print("--- file demo ---")
Player.savePlayersToDisk(playersArray)
if let retreivedPlayers = Player.loadPlayersFromDisk() {
print("loaded \(retreivedPlayers.count) players from disk")
print("\(retreivedPlayers[0].name)")
print("\(retreivedPlayers[1].name)")
} else {
print("failed")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
as said above, both methods produce the same result
Also in a real life application you could do better error handling in the, as archiving & unarchiving could fail.
I have a class
class Player {
var name = ""
init(name: String) {
self.name = name
}
}
and i want to serialise it and save to user defaults.
In Swift 4 / iOS 11, there's a whole new way to do this. It has the advantage that any Swift object can use it — not just classes, but also structs and enums.
You'll notice that I've omitted your NSCoding-related methods, because you won't need them for this purpose. You can adopt NSCoding here, as you know; but you don't have to. (And a struct or enum cannot adopt NSCoding at all.)
You start by declaring your class as adopting the Codable protocol:
class Player : Codable {
var name = ""
init(name: String) {
self.name = name
}
}
It then becomes a simple matter to serialize it into a Data object (NSData) which can be stored in UserDefaults. The very simplest way is to use a property list as an intermediary:
let player = Player(name:"matt")
try? UserDefaults.standard.set(PropertyListEncoder().encode(player),
forKey:"player")
If you use that approach, let's now prove that you can pull the same Player back out of UserDefaults:
if let data = UserDefaults.standard.object(forKey:"player") as? Data {
if let p = try? PropertyListDecoder().decode(Player.self, from: data) {
print(p.name) // "matt"
}
}
If you'd rather pass through an NSKeyedArchiver / NSKeyedUnarchiver, you can do that instead. Indeed, there are some situations where you'll have to do so: you'll be presented with an NSCoder, and you'll need to encode your Codable object inside it. In a recent beta, Xcode 9 introduced a way to do that too. For example, if you're encoding, you cast the NSCoder down to an NSKeyedArchiver and call encodeEncodable.
With codable new ios 11 protocol you can now let your class implements it and archive/unarchive objects of it with JSONEncoder and JSONDecoder
struct Language: Codable {
var name: String
var version: Int
}
let swift = Language(name: "Swift", version: 4)
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(swift) {
// save `encoded` somewhere
}
if let encoded = try? encoder.encode(swift) {
if let json = String(data: encoded, encoding: .utf8) {
print(json)
}
let decoder = JSONDecoder()
if let decoded = try? decoder.decode(Language.self, from: encoded) {
print(decoded.name)
}

Resources