How to check a single time if UserDefaults is empty - ios

My app counts the days between a date and NSDate(). When I released it, a user could only save one date, a title and a background image.
Now I have a UICollectionView with the option to save more than one date, and it will create a cell by appending a date, title and image string to their respective arrays.
The app has been completely changed, so I'm struggling with how to check whether a user has saved a date, and if they have, add that info to the arrays to create a cell.
But, I want this to only been checked once - the first time the app is opened from update or fresh install.
Here is my thinking about it, it doesn't work by the way.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! MyCollectionViewCell
if userDefault.objectForKey("day") == nil {
} else {
// Add the first date created from previous version
let day = userDefault.objectForKey("day") as? String
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd MMMM yyyy hh:mm a"
let date = dateFormatter.dateFromString(day!)!
let date1 = dateFormatter.stringFromDate(date)
addDateToArray(date1)
// Add the since text
let text = userDefault.objectForKey("sinceText") as? String
addSinceLabelToArray(text!)
//Add the image background
let image = userDefault.objectForKey("khoury") as! String
addThemeToImagesArray(image)
}
What happens with the code above is it returns nil. I am expecting it to create the first cell, so that the user can see the date they have saved.

You can use another boolean value in NSUserDefaults to detect the first run:
let defaults = NSUserDefaults.standardUserDefaults()
if (!defaults.boolForKey("migrated")) {
defaults.setBool(true, forKey: "migrated")
// ... anything else
}

Create an array within your view controller.
var dates = [NSDate]()
Then in viewDidLoad:
if let retrieved = userDefault.objectForKey("day") {
dates = retrieved as! [NSDate]
}
Then reload your collection view

Related

How to change the backgroungColor cell in tableView with timeInterval using Swift

I'm trying to change the cell backgroungColor after 3.5 month. I have a textField where i put the date and after 3.5 month of that date I want to change the color of the cell in red.
I tried this where date1 is the date from textField and date2 is this from (isToday) where i have put 106 day = 3.5 month
let isToday= Date.now.addingTimeInterval(106)
func isSameDay(date1: Date, date2: Date) -> Bool {
let diff = Calendar.current.dateComponents([.day], from: date1, to: date2)
if diff.day == 0 {
return true
}
else {
return false
}
}
Inside cellForRowAt i have used like this
var documentSendDate = "05.08.2022"// this is example to be more understandable
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM/dd/yyyy"
let date = dateFormatter.date(from: documentSendDate)
if date != nil {
let isDayToday = isSameDay(date1: date!, date2: isToday) // here I call the function above
if isDayToday == true {
if customer.isToday == true {
cell.backgroundColor = .red
}
}
}
But I have this checkBox and when I check it or uncheck it change the color of the random cells. Can someone help me with this please?
Here is how i wanted to look.
UITableView is a recycle-list view, so it will reuse the cell UI instance to display data for the corresponding indexPath.
First, modify your code to add a new way of dateFormatter declaration.
// Use lazy var to reduce initialization cost
// Because initializing a new DateFormatter is not cheap, it can consume CPU time like initializing a new NumberFormatter
// lazy var will be only initialized once on the first call/use
lazy var dateFormatter = DateFormatter()
func viewDidLoad() {
super.viewDidLoad()
dateFormatter.dateFormat = "MMM/dd/yyyy"
}
It is a reusable UI, so UI won't hold the data or state. The cellForRowAt will be called multiple times when you scroll tableView or when tableView needs to re-layout,... to display the corresponding data-state for each indexPath.
That is why you must not initialize or do some big calculations/long waiting here. It will freeze/delay your UI (ref: DispatchQueue.main or MainQueue).
So inside your cellForRowAt function, you need to add logic for all cases if you use switch/if-else.
var documentSendDate = "05.08.2022"// this is example to be more understandable
// Here I combine all checks into one if-else
// Order of check is left-to-right.
// It is condition1 AND condition2 AND condition3 (swift syntax)
if let date = dateFormatter.date(from: documentSendDate),
let isDayToday = isSameDay(date1: date!, date2: isToday),
customer.isToday == true {
cell.backgroundColor = .red
} else {
cell.backgroundColor = .clear // or your desired color
}

Fetching and displaying data from core data

Aim :
To be able to display the days selected and the time picked by the user in the same row of the table view. The time should appear at the top and the days selected should appear at the bottom, both in the same row, just like an alarm clock.
Work :
This is the relationship I've got setup :
and this is how I save the days that are selected from a UITable and the time from a UIDatepicker when the save button is tapped :
#IBAction func saveButnTapped(_ sender: AnyObject)
{
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext //creates an object of a property in AppDelegate.swift so we can access it
let bob = Bob(context: context)
//save the time from UIDatePicker to core data
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
bob.timeToPing = dateFormatter.string(from: timePicked.date)
// save the days selected to core data
for weekday in filteredWeekdays
{
var day = Days(context: context) //create new Days object
day.daysSelected = weekday as NSObject? //append selected weekday
bob.addToTimeAndDaysLink(day) //for every loop add day object to bob object
}
//Save the data to core data
(UIApplication.shared.delegate as! AppDelegate).saveContext()
//after saving data, show the first view controller
navigationController!.popViewController(animated: true)
}
Now that the data is once saved, I get the data :
func getData()
{
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do
{
bobs = try context.fetch(Bob.fetchRequest())
}
catch
{
print("Fetching failed")
}
}
Attempt to get the days selected :
I tried to follow this, the below comments and a formerly deleted answer to this question to do this :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell()
let bob = bobs[indexPath.row]
cell.textLabel?.text = bob.timeToPing?.description
// retrieve the days that are selected
var daysArray: [Days] = []
daysArray = bob.timeAndDaysLink?.allObjects as! [Days]
for days in daysArray
{
print (days.daysSelected?.description)
cell.textLabel?.text = days.daysSelected! as! String
}
return cell
}
EDIT :
print(daysArray) gives this :
[<Days: 0x6080000a5880> (entity: Days; id: 0xd000000000040000 <x-coredata://30B28771-0569-41D3-8BFB-D2E07A261BF4/Days/p1> ; data: <fault>)]
print(daysArray[0]) gives this :
<Days: 0x6080000a5880> (entity: Days; id: 0xd000000000040000 <x-coredata://30B28771-0569-41D3-8BFB-D2E07A261BF4/Days/p1> ; data: <fault>)
How to save days
let weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
var filteredWeekdays: [String] = []
#NSManaged public var daysSelectedbyUser: NSSet
And then
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
selectedWeekdays()
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath)
{
selectedWeekdays()
}
func selectedWeekdays()
{
if let selectedRows = tableView.indexPathsForSelectedRows
{
let rows = selectedRows.filter {$0.section == 0}.map{ $0.row}
filteredWeekdays = rows.map{ weekdays[$0] }
print(filteredWeekdays)
}
}
Many thanks!
OK based on your latest comment that the crash occur on this line:
cell.textLabel?.text = days.value(forKey: "daySelected") as! String
It's clearly pointing to the typo you've made in key name. You have: daySelected and should be (based on your core data model) daysSelected, but nevertheless it's not very good approach to use values for your core data entity and also force type like that. To make it better I suggest replacing this line with:
cell.textLabel?.text = days.daysSelected!
This should be already a String since this is a String in CoreData. In case it's optional (should be an optional), you shouldn't force it. I will assume that whenever data will be not there you will just display empty cell, so even better it will be:
cell.textLabel?.text = days.daysSelected ?? ""
This will produce empty string to text, whenever (for some reason) data will be not there.
EDIT
So for additional piece of code you put in your question:
In your CoreData field daysSelected is type of String?, right?
Then you assign timeAndDateLink to NSSet<String>, right? But expected value here should be NSSet<Days>.
So let's edit your input code a bit ( i will put comment on every line):
let bob = Bob(context: context) /* create new Bob object */
for weekday in filteredWeekdays {
var day = Days(context: context) /* create new Days object */
day.daysSelected = weekday /* append selected weekday */
bob.addToTimeAndDaysLink(day) /* for every loop add day object to bob object */
}
I hope everything is clear in above example. You may have a problem with a compiler in that case, because if you choose generate class for entities you will endup with two func with the same name but different parameter (in Swift this should be two different functions, but Xcode sometimes pointing to the wrong one). If you hit that problem try:
let bob = Bob(context: context) /* create new Bob object */
var output: NSMutableSet<Days> = NSMutableSet()
for weekday in filteredWeekdays {
var day = Days(context: context) /* create new Days object */
day.daysSelected = weekday /* append selected weekday */
output.add(day)
}
bob.addToTimeAndDaysLink(output) /* this will assign output set to bob object */
You should also rename your Days entity to Day to avoid future confusion that we have right now, days as array will only be in relation from other entities to this not entity itself.
I don't know why no one uses FetchedResultsController, which is made for fetching NSManagedObjects into tableView, but it doesn't matter I guess...
Problem in this question is that you didn't post here your NSManagedObject class for the variable, so I cannot see which type you set there (Should be Transformable in CoreData model and [String] in NSManagedObject class...)
Ignoring all force unwraps and force casting and that mess (which you should pretty damn well fix as first, then it won't crash at least but just don't display any data...)
Days selected by user is NSSet, which it sure shouldn't be.
Please provide you NSManagedObjectClass in here so I can edit this answer and solve your problem...

PrepareForSegue for dynamic Array in UITableView

I want to make programmatically segue from TableView. Content of the cells is dynamic - the're changing, so I can't wrap segue to the number of row (ex. indexPath.row = 1). My array is like that:
myArray = [value1, value2, value3]
But value1 can be today "A", but tomorrow will be "B". So today value1 should redirect to AController, but tomorrow - to BController. Name of the value is of course displayed in the TableView.
I suppose prepareForSegue should be based on name of the value (ex. if name of the value is 'A', then...). But I don't know the method.
Any help will be appreciated :)
To be more clear - how my array is generated:
let cal = NSCalendar.currentCalendar()
let fmt = NSDateFormatter()
var countDays = [String]()
override func viewDidLoad() {
super.viewDidLoad()
fmt.dateFormat = "EEEE"
fmt.locale = NSLocale(localeIdentifier: "pl_PL")
var date = cal.startOfDayForDate(NSDate())
while countDays.count < 7 {
let weekDay = cal.component(.Weekday, fromDate: date)
if weekDay != 0 {
countDays.append(fmt.stringFromDate(date))
}
date = cal.dateByAddingUnit(.Day, value: 1, toDate: date, options: NSCalendarOptions(rawValue: 0))!
}
print(countDays)
You can use "didselectRowAtIndex" delegate method of tableview. You will get the current index path of cell which user has selected/clicked.Use this index path to retrieve corresponding object in your array.Next check the value in retrieved object "A" or "B" using if/else, depending on this you can launch your "AController" or "Controller".Use prepare for segue to launch you specific controller.
Note : all this logic should be done in you "didselectRowAtIndex" method.
Hope this helps.
Ok, I solved this with code:
let cell: UITableViewCell =
tableView.cellForRowAtIndexPath(indexPath)!
let str: String = cell.textLabel!.text!
if str.containsString("A") {
performSegueWithIdentifier("AMonday", sender:self)
}
Easy, but finding this was painful ;)

Choppy scroll on UITableView displaying images from URL

I have an issue with my table view being choppy on scroll while it loads each image inside the function of cellForItemAtIndexPath i've searched through some examples and these are the things i've tried and still have the same issue.
So i have this
var arrRes = [[String:AnyObject]]()
Then inside view did load i make an Alamofire GET request and with swiftyJSON i store the json file to the above dictionary.
if let resData = swiftyJsonVar["events"].arrayObject {
self.arrRes = resData as! [[String:AnyObject]]
}
self.tableview2.reloadData()
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrRes.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("mapCell", forIndexPath: indexPath) as! locationEventsTableViewCell
var dict = arrRes[indexPath.row]
cell.eventTitle.text = dict["eventName"] as? String
let cal = dict["eventStarttime"] as? String
let dateF = NSDateFormatter()
dateF.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let date:NSDate = dateF.dateFromString(cal!)!
let d = NSDateFormatter()
d.dateFormat = "dd-MM-yyyy HH:mm"
let d1 = d.stringFromDate(date)
cell.eventDate.text = d1
let at = dict["eventStats"]?["attendingCount"] as? Int
let kapa = at?.stringValue
cell.eventAttends.text = kapa
let imageDef : UIImage = UIImage(named: "noimage")!
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
if let theImage = dict["eventCoverPicture"] as? String {
let url = NSURL(string: theImage)
if url != nil {
let data = NSData(contentsOfURL: url!)
dispatch_async(dispatch_get_main_queue()) {
cell.eventImage.image = UIImage(data: data!)
}
} else {
cell.eventImage.image = imageDef
}
}
}
return cell
}
So as you can see i am using the dispatch async function to get the image and even if i have it or not its still choppy.
Has anyone any solution about this? Thanks!
So the problem is that you're calling the images from a URL each time your UITableView is showing. Every time the cell goes off screen and comes back it's calling the method to retrieve the image from the server.
The server calls are being performed while the UI is trying to execute, this includes the scrolling and other visual loads.
Depending on the app, you can download all the images for the UITableView before you load the tableView and store them locally. I would also look into NSCache as that might be better for your app.
The goal is to have UI always be the number one priority. So if there are things that need to be in the UITableView like your eventCoverPicture, load them or call them from memory before you load the UITableView.
This ensures you're making the minimum amount of server calls necessary to reduce user network load.
The UI is interrupted and your users can scroll through their app without this choppiness.
I think your code is right about Async api. it's possibly the NSDateFormatter slowing you down. Date formatter is an heavy API. Use memorization, it would improve the prformance as well.
class MyObject {
// define static variable
private static let formatter: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy"
return formatter
}()
// you could use it like so
func someMethod(date: NSDate) -> String {
return MyObject.formatter.stringFromDate(date)
}
}

Swift: app crashes after deleting a column in Parse

In Parse I accidentally deleted a column called "likes" that counts the number of a likes a user receives for their blog post. I created the column again with the same name but now when I run my app it crashes and I receive this message "unexpectedly found nil while unwrapping an Optional value". It points to my code where its suppose to receive the "likes" in my cellForRowAtIndexPath. I pasted my code below. Is there any way I could fix this issue and stop it from crashing?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath?) -> PFTableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("BCell", forIndexPath: indexPath!) as! BlogCell
if let object : PFObject = self.blogPosts.objectAtIndex(indexPath!.row) as? PFObject {
cell.author.text = object["blogger"] as? String
cell.post.text = object["blogPost"] as? String
let dateUpdated = object.createdAt! as NSDate
let dateFormat = NSDateFormatter()
dateFormat.dateFormat = "h:mm a"
cell.timer.text = NSString(format: "%#", dateFormat.stringFromDate(dateUpdated)) as String
let like = object[("likes")] as! Int
cell.likeTracker.text = "\(like)"
}
return cell
}
I would inspect what's going on with the object if I were you. You clearly aren't getting data that you expected to be there. As a stopgap, you can change let like = object["likes"] as! Int to
if let like = object["likes"] as? Int {
cell.likeTracker.text = "\(like)"
}
If you do that, you will also want to implement the prepareForReuse method in BlogCell to set that label's text to nil or else you might have some weird cell reuse bugs.
Where you delete a column from uitableview , you need to delete data from data source, and update the delete index or reload the whole table .
Look for if you are missing that step

Resources