NSKeyedUnarchiver.unarchiveObject() unarchives old object - swift

I want to save the user's filter selections on FilterViewController.
When FilterViewController is closed, NSKeyedArchiver.archiveRootObject archives the user's selections. However, NSKeyedUnarchiver.unarchiveObject opens up the initial default selections (NOT the user's selections). How to fix this?
FiltersViewController.swift
override func viewWillAppear(_ animated: Bool) {
if let filterSections = NSKeyedUnarchiver.unarchiveObject(withFile: filterViewModel.filtersFilePath) as? [FilterSection] {
// Retrieves initial default selections, NOT user's selection
filterViewModel.filterSections = filterSections
filtersTableView.reloadData()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Saves what user selects
let isSuccessful = NSKeyedArchiver.archiveRootObject(self.filterViewModel.filterSections, toFile: self.filterViewModel.filtersFilePath)
if (isSuccessful) {
print("Saved filters") // This is printed
} else {
print("Didn't Save filters")
}
}
FilterViewModel.swift
class FilterViewModel: NSObject {
// Contains all filtered sections displayed on table view
var filterSections: [FilterSection] = []
// File Path to saved Filter Sections
var filtersFilePath: String {
let manager = FileManager.default
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
print("this is the url path in the documentDirectory \(url)")
return (url!.appendingPathComponent("FilterSelectionData").path)
}
override init() {
super.init()
filterSections = self.getFilterSections()
}
}
CompanyViewController.swift
#objc func filterButtonTapped() {
var filterViewModel: FilterViewModel
if (self.filterViewModel != nil) {
filterViewModel = self.filterViewModel! // This runs
}
else {
self.filterViewModel = FilterViewModel()
filterViewModel = self.filterViewModel!
}
let filtersVC = FiltersViewController(filterViewModel: filterViewModel)
self.navigationController?.pushViewController(filtersVC, animated: true)
}

You are using self.getFilterSections to set filterSections in FilterViewModel init. I suppose self.getFilterSections is a method that returns the default values. For me, it should not be the case, rather if you have archived some values, you should get that in this method. Although, this alone should not be the reason for the issue, but may be a reason for inducing bug. Try changing self.getFilterSections to return archived values if possible otherwise default values and check whether the bug is still there.

Related

fatal errors with optionals not making sense

I keep getting a fatal error saying how a value was unwrapped and it was nil and I don't understand how. When I instantiate a view controller with specific variables they all show up, but when I perform a segue to the exact VC, the values don't show up.
Take these functions for example...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let displayVC = storyboard?.instantiateViewController(withIdentifier: Constants.Storyboards.TeachStoryboardID) as? SchoolEventDetailsViewController {
displayVC.selectedEventName = events[indexPath.row].eventName
displayVC.selectedEventDate = documentsDate[indexPath.row].eventDate
displayVC.selectedEventCost = documentsCost[indexPath.row].eventCost
displayVC.selectedEventGrade = documentsGrade[indexPath.row].eventGrade
displayVC.selectedEventDocID = documentsID[indexPath.row]?.docID
navigationController?.pushViewController(displayVC, animated: true)
}
}
This combined with this function :
func verifyInstantiation() {
if let dateToLoad = selectedEventDate {
dateEditableTextF.text = dateToLoad
}
if let costToLoad = selectedEventCost {
costEditableTextF.text = costToLoad
}
if let gradesToLoad = selectedEventGrade {
gradesEditableTextF.text = gradesToLoad
}
if let docIDtoLoad = selectedEventDocID {
docIDUneditableTextF.text = docIDtoLoad
}
if let eventNameToLoad = selectedEventName {
eventNameEditableTextF.text = eventNameToLoad
}
}
Helps load the data perfectly, but when I try to perform a segue from a search controller the data is not there.
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = selectedEventName
I set the title of the vc to have the event name , and I also recently added a text field to store it as well for experimental purposes (this question).
Now the issue is I want to do a data transfer from an Algolia Search Controller to that VC and I got all the other fields to show up, except for one and that was the document ID. So I created a completion handler function to get the document ID as a string and have it inserted into the vc when the segue is performed, just like how it's there when the vc is instantiated.
Here is the function :
func getTheEventDocID(completion: #escaping ((String?) -> ())) {
documentListener = db.collection(Constants.Firebase.schoolCollectionName).whereField("event_name", isEqualTo: selectedEventName ?? navigationItem.title).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
if let error = error {
print("There was an error fetching the documents: \(error)")
} else {
self.documentsID = querySnapshot!.documents.map { document in
return EventDocID(docID: (document.documentID) as! String)
}
let fixedID = "\(self.documentsID)"
let substrings = fixedID.dropFirst(22).dropLast(3)
let realString = String(substrings)
completion(realString)
}
}
}
I thought either selectedEventName or navigationItem.title would get the job done and provide the value when I used the function in the data transfer function which I will show now :
//MARK: - Data Transfer From Algolia Search to School Event Details
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
otherVC.getTheEventDocID { (eventdocid) in
if let id = eventdocid {
if segue.identifier == Constants.Segues.fromSearchToSchoolEventDetails {
let vc = segue.destination as! SchoolEventDetailsViewController
vc.selectedEventName = self.nameTheEvent
vc.selectedEventDate = self.dateTheEvent
vc.selectedEventCost = self.costTheEvent
vc.selectedEventGrade = self.gradeTheEvent
vc.selectedEventDocID = id
}
}
}
}
But it ends up showing nothing when a search result is clicked which is pretty upsetting, I can't understand why they're both empty values when I declared them in the SchoolEventDetailsVC. I tried to force unwrap selectedEventName and it crashes saying there's a nil value and I can't figure out why. There's actually a lot more to the question but I just tried to keep it short so people will actually attempt to read it and help since nobody ever reads the questions I post, so yeah thanks in advance.
I'm a litte confused what the otherVC is, which sets a property of itself in the getTheEventDocID, whilste in the closure you set the properties of self, which is a different controller. But never mind, I hope you know what you are doing.
Since getTheEventDocID runs asynchronously, the view will be loaded and displayed before the data is available. Therefore, viewDidLoad does not see the actual data, but something that soon will be outdated.
So, you need to inform the details view controller that new data is available, and refresh it's user interface. Something like
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
otherVC.getTheEventDocID { (eventdocid) in
if let id = eventdocid {
if segue.identifier == Constants.Segues.fromSearchToSchoolEventDetails {
let vc = segue.destination as! SchoolEventDetailsViewController
vc.selectedEventName = self.nameTheEvent
vc.selectedEventDate = self.dateTheEvent
vc.selectedEventCost = self.costTheEvent
vc.selectedEventGrade = self.gradeTheEvent
vc.selectedEventDocID = id
vc.updateUI()
}
}
}
}
and in the destination view controller:
class SchoolEventDetailsViewController ... {
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
func updateUI () {
navigationItem.title = selectedEventName
// and so on
}
}
Ok so I decided to attempt a workaround and completely ditched the getTheEventDocID() method because it was just causing me stress. So I decided to ditch Firebase generated document IDS and just use 10 digit generated ids from a function I made. I also figured out how to add that exact same 10 digit id in the Algolia record by just storing the random 10 digit id in a variable and using it in both places. So now instead of using a query call to grab a Firebase generated document ID and have my app crash everytime I click a search result, I basically edited the Struct of the Algolia record and just added an eventDocID property that can be used with hits.hitSource(at: indexPath.row).eventDocID.
And now the same way I added the other fields to the vc by segue data transfer, I can now do the same thing with my document ID because everything is matching :).

Download single Object of Firestore and save it into an struct/class object

I am coding since January 2019 and this is my first post here.
I am using Swift and Firestore. In my App is a tableView where I display events loaded out of a single Document with an array of events inside as [String: [String:Any]]. If the user wants to get more infos about an event he taps on it. In the background the TableViewController will open a new "DetailEventViewController" with a segue and give it the value of the eventID in the tapped cell.
When the user is on the DetailViewController Screen the app will download a new Document with the EventID as key for the document.
I wanna save this Data out of Firestore in a Struct called Event. For this example just with Event(eventName: String).
When I get all the data I can print it directly out but I can't save it in a variable and print it out later. I really don't know why. If I print the struct INSIDE the brackets where I get the data its working but if I save it into a variable and try to use this variable it says its nil.
So how can I fetch data out of Firestore and save in just a Single ValueObject (var currentEvent = Event? -> currentEvent = Event.event(for: data as [String:Any]) )
I search in google, firebaseDoc and stackoverflow but didn't find anything about it so I tried to save all the singe infos of the data inside a singe value.
// Struct
struct Event {
var eventName: String!
static func event(for eventData: [String:Any]) -> Event? {
guard let _eventName = eventData["eventName"] as? String
else {
print("error")
return nil
}
return Event(eventName: _eventName)
}
// TableView VC this should work
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEventDetailSegue" {
if let ShowEvent = segue.destination as? DetailEventViewController, let event = eventForSegue{
ShowEvent.currentEventId = event.eventID
}
}
}
// DetailViewController
var currentEvent = Event()
var currentEventId: String?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let _eventID = currentEventId else {
print("error in EventID")
return}
setupEvent(eventID: _eventID) /* currentEvent should be set here */
setupView(event: currentEvent) /* currentEvent has after "setupEvent" the value of nil */
}
func setupEvent(eventID: String) {
let FirestoreRef = Firestore.firestore().collection("events").document(eventID)
FirestoreRef.getDocument { (document, error) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
SVProgressHUD.showError(withStatus: "Error in Download")
}else {
if let document = document, document.exists {
guard let data = document.data() else {return}
let eventData = Event.event(for: data as [String:Any])
print(eventData)
//here all infos are printed out - so I get them
self.currentEvent = eventData!
//Here is the error.. I can't save the fetched Data in my single current Event
} else {
SVProgressHUD.showError(withStatus: "Error")
}
}
}
}
func setupView(event: Event) {
self.titleLabel.text = event.eventName
}
I expect that the function setupEvents will give the currentEvent in the DetailViewController a SINGLEvalue cause its a SINGLE document not an array. So I can use this single Eventvalue for further actions. Like starting a new segue for a new ViewController and just push the Event there not

How to extract a zip file and get the extracted components in a Share Extension in Swift

I need to do the following-
I have another app in which i will export the users config(.txt) and contacts(.vcf) in a zip format.
In the second app i have a share extension to get the exported zip and in the share extension, i need to extract the zip file and get both the txt and vcf files and then upload them to a parse server.
I have done till opening the exported zip in the share extension. but i could not get the zip extracted.
I couldn't get the answer in internet.
Here is my ShareViewController
import UIKit
import Social
import Parse
import MobileCoreServices
import SSZipArchive
class ShareViewController: SLComposeServiceViewController {
var requird_data : NSData!
var path : URL!
override func viewDidLoad() {
super.viewDidLoad()
//Parse.setApplicationId("cGFyc2UtYXBwLXdob3N1cA==", clientKey: "")
initUI()
getURL()
textView.delegate = self
textView.keyboardType = .numberPad
}
// override func viewWillAppear(_ animated: Bool) {
// super.viewWillAppear(true)
//
// }
func initUI()
{
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
title = "upup"
navigationController?.navigationBar.tintColor = .white
navigationController?.navigationBar.backgroundColor = UIColor(red:0.97, green:0.44, blue:0.12, alpha:1.00)
placeholder = "Please enter your Phone number"
}
private func getURL() {
let extensionItem = extensionContext?.inputItems.first as! NSExtensionItem
let itemProvider = extensionItem.attachments?.first as! NSItemProvider
let zip_type = String(kUTTypeZipArchive)
if itemProvider.hasItemConformingToTypeIdentifier(zip_type) {
itemProvider.loadItem(forTypeIdentifier: zip_type, options: nil, completionHandler: { (item, error) -> Void in
guard let url = item as? NSURL else { return }
print("\(item.debugDescription)")
OperationQueue.main.addOperation {
self.path = url as URL
SSZipArchive.unzipFile(atPath: url.path!, toDestination: url.path!)
}
})
} else {
print("error")
}
}
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
return true
}
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
override func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
{
let length = ((textView.text)?.characters.count)! + text.characters.count - range.length
let allowedset : CharacterSet = CharacterSet(charactersIn: "0123456789+").inverted as CharacterSet
let filtered = (text.components(separatedBy: allowedset)).joined(separator: "")
return (length<17) && (text == filtered)
}
}
I use SSZipAchive to extract the file. Link : https://github.com/ZipArchive/ZipArchive
I ran the application in the Xcode 9 beta 1. I used the new Files app from simulator to share the zip.
Below is my Share Extensions Info.Plist
I am newbie to share extension so i don't know much about it. All the code above are from bits and pieces from the following tutorials and a little googling.
1.https://www.appcoda.com/ios8-share-extension-swift/
2.https://hackernoon.com/how-to-build-an-ios-share-extension-in-swift-4a2019935b2e
Please guide me.
I use swift 3.
I found out the solution. It was my mistake to give the destination file path for the extracted items to be the same as the source files path. After changing it to the app's documents directory i got it working.
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
SSZipArchive.unzipFile(atPath: url.path!, toDestination: documentsPath)

How to reload UITableView without printing data twice?

I have a single view of SlackTextViewController which works as a UITableView. I'm switching between "states" using a public String allowing it to read 1 set of data in a certain state and another set of data in another state. The problem is when I switch back and forth to the original state it prints the data 2, 3, 4 times; as many as I go back and forth. I'm loading the data from a Firebase server and I think it may be a Firebase issue. Here is my code...
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if chatState == "ALL"
{
self.globalChat()
}else if chatState == "LOCAL"
{
self.localChat()
}
}
override func viewDidDisappear(animated: Bool) {
self.messageModels.removeAll()
tableView.reloadData()
ref.removeAllObservers()
}
func chatACTN()
{
if chatState == "ALL"
{
self.viewWillAppear(true)
}else if chatState == "LOCAL"
{
self.viewWillAppear(true)
}
}
func globalChat()
{
self.messageModels.removeAll()
tableView.reloadData()
let globalRef = ref.child("messages")
globalRef.keepSynced(true)
globalRef.queryLimitedToLast(100).observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
if snapshot.exists()
{
let names = snapshot.value!["name"] as! String
let bodies = snapshot.value!["body"] as! String
let avatars = snapshot.value!["photo"] as! String
let time = snapshot.value!["time"] as! Int
let messageModel = MessageModel(name: names, body: bodies, avatar: avatars, date: time)
self.messageModels.append(messageModel)
self.messageModels.sortInPlace{ $0.date > $1.date }
}
self.tableView.reloadData()
})
}
func localChat()
{
self.messageModels.removeAll()
tableView.reloadData()
print("LOCAL")
}
The problem is that each time you call globalChat() you're creating another new observer which results in having multiple observers adding the same items to self.messageModels. Thats why you're seeing the data as many times as you switch to the global state.
Since you want to clear the chat and load the last 100 each time you switch to global, there's no point in keeping the observer active when you switch to "Local".
Just remove the observer when you switch to Local, that should fix your problem.
From firebase docs:
- (void) removeAllObservers
Removes all observers at the current reference, but does not remove any observers at child references.
removeAllObservers must be called again for each child reference where a listener was established to remove the observers.
So, ref.removeAllObservers() will not remove observers at ref.child("messages") level.
Using ref.child("messages").removeAllObservers at the beginning of localChat function to remove the observer you created in globalChat would be ok if you're only dealing with this one observer at this level but if you have more on the same level or you think you might add more in the future the best and safest way would be to remove the specific observer you created. To do that you should use the handle that is returned from starting an observer. Modify your code like this:
var globalChatHandle : FIRDatabaseHandle?
func globalChat()
{
self.messageModels.removeAll()
tableView.reloadData()
ref.child("messages").keepSynced(true)
globalChatHandle = ref.child("messages").queryLimitedToLast(100).observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
if snapshot.exists()
{
let names = snapshot.value!["name"] as! String
let bodies = snapshot.value!["body"] as! String
let avatars = snapshot.value!["photo"] as! String
let time = snapshot.value!["time"] as! Int
let messageModel = MessageModel(name: names, body: bodies, avatar: avatars, date: time)
self.messageModels.append(messageModel)
self.messageModels.sortInPlace{ $0.date > $1.date }
}
self.tableView.reloadData()
})
}
func localChat()
{
if globalChatHandle != nil {
ref.child("messages").removeObserverWithHandle(globalChatHandle)
}
self.messageModels.removeAll()
tableView.reloadData()
print("LOCAL")
}
And in viewDidDisappear method replace this
ref.removeAllObservers()
with
ref.child("messages").removeAllObservers()

Game center Authentication not working

My Game Center Authentication is not working. When I build and run, it won't show my username.. has signed in. Also, when I try to add my score I get a screen that says "no data availible". Heres my code.
override func viewDidLoad() {
super.viewDidLoad()
gcAuthPlayer()
}
#IBAction func GCButton(sender: AnyObject) {
saveHighScore(GameScene().highScoreNumer)
showLeaderBoard()
if GameScene().currentScore > GameScene().highScoreNumer{
saveHighScore(GameScene().currentScore)
}
}
func showLeaderBoard(){
let viewController = self.view.window?.rootViewController
let gcvc = GKGameCenterViewController()
gcvc.gameCenterDelegate = self
viewController?.presentViewController(gcvc, animated: true, completion: nil)
}
func saveHighScore(number: Int){
if GKLocalPlayer.localPlayer().authenticated{
let scoreReporter = GKScore(leaderboardIdentifier: "myleaderboard")
scoreReporter.value = Int64(number)
let scoreArray : [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: nil)
}
}
func gcAuthPlayer(){
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {
(view, error) in
if view != nil{
self.presentViewController(view!, animated: true, completion: nil)
}else{
print(GKLocalPlayer.localPlayer().authenticated)
}
}
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
This code makes no sense
saveHighScore(GameScene().highScoreNumer)
showLeaderBoard()
if GameScene().currentScore > GameScene().highScoreNumer{
saveHighScor
You are creating a new instance of GameScene everytime you try to update the score and therefore your score is nil
I would need to see some more code but for now you need to change the score property in your game scene. For example make it a static property so you can get it in other classes.
class GameScene: SKScene {
static var currentScore = 0
static var highscoreNumber = 0
}
Than in your Scenes or ViewController you can get it like so
GameScene.currentScore = 5
GameScene.highscoreNumber = 5
Just remember that you have to reset the score to 0 everytime you restart your gameScene because it a static property.
GameScene.currentScore = 0
GameScene.highscoreNumber = 0
Than your code to post the score should look like this
saveHighScore(GameScene.highScoreNumer)
showLeaderBoard()
if GameScene.currentScore > GameScene.highScoreNumer{
saveHighScor
Your score reporting code should also handle the error and actually do the completion handler. So change it to something like this.
/// Save leaderboard progress
func reportLeaderboardProgress(value: Int, leaderboardID: String) {
let scoreReporter = GKScore(leaderboardIdentifier: leaderboardID)
scoreReporter.value = Int64(value)
GKScore.reportScores([scoreReporter]) { error in // Trailing Closure syntax
if let error = error {
print(error.localizedDescription)
return
}
print("Reported leaderboard progress \(value) to leaderboardID \(leaderboardID)")
}
}
It is also a good idea to move that code into another class to keep your overall code cleaner and more reusable.
For a nice and simple example check this helper on gitHub.
https://github.com/jackcook/GCHelper
Let me know how it goes.