I'm trying to recreate the feed on the facebook app and I'm having trouble inserting the newly downloaded cells. The cells are being inserted, but like this SO question: Trying to insert new items into UICollectionView but getting the same cells over and over , it keeps inserting the same results, the initial first 10 posts in this case.
What i've done is this:
jsonRequest.getPosts(hashKey!, request: populateFeedRequest) { (succeeded:Bool, msg:String, results:[AnyObject]) -> () in
var resultsAreDone:Bool!
if succeeded {
if (results.isEmpty == false) {
++self.lastFeedPostId
resultsAreDone = true
} else {
resultsAreDone = false
}
} else {
println("you weren't able to download the posts")
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if (resultsAreDone == true) {
var updatedArray:Array = [Dictionary<String, AnyObject>]()
self.postsView.performBatchUpdates({
let resultsSize = self.feedArray.count
var size = 0
if let newArray = results as? [Dictionary<String, AnyObject>] {
for n in newArray {
self.feedArray.append(n)
}
size = resultsSize + newArray.count
}
var arrayWithIndexPaths = NSMutableArray()
var i = 0
for (i = resultsSize; i < size; i++) {
arrayWithIndexPaths.addObject(NSIndexPath(forRow: i, inSection: 0))
}
self.postsView.insertItemsAtIndexPaths(arrayWithIndexPaths as [AnyObject])
},
completion: nil)
}
else {
println("it didn't work")
}
})
}
the lastFeedPostId is used to grab the next 10 results from the server.
Also a sidenote: i'm doing this in the "cellforItemAtIndexPath" function. I'm using the current Indexpath to determine if I should pull the next 10 posts from the server or not. Is this correct, or is there a better way to do this?
Thank you so much!!
Related
EDITED
I have a UITableView which displays multiple social media posts. I use the Prefetch Source Delegate for prefetching Posts and I use Core Data to be stored after being fetched from web server. Problem that I have is that I get no error, but the data does not stay saved between launches in CoreData.
Snip of code
func configureDataSourceTable(){
self.dataSource = UITableViewDiffableDataSource<String,String>(tableView: self.table_view){
(tableView, indexPath, ref) -> UITableViewCell? in
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CheckInCell
if !self.postData.currentPostFetching(ref: ref){
self.postData.fetchPost(ref: ref) { post in
DispatchQueue.main.async {
self.setPostNeedsUpdate(ref: ref)//reconfigure items
}
return cell
}
func fetchPost(ref: String,completion : (PostStruct)->Void) {
if self.dataContainer.postIsPartOfCoreData(ref: ref){
var postStruct = self.dataContainer.getPostStructFromDB(ref: ref)
completion(postStruct)
}else{ //has to be downloaded from web server
Task {
let post = await getPostFromServer()//not relevant in understanding problem
var postStruct = self.convertDataToPost(post)
completion(postStruct)
self.dataContainer.savePostStruct(post: postStruct)
}
}
}
Class of the DataContainer subclass of NsPersistentStore
func savePostStruct(post_struct : PostStruct,image_data : String){
Task.detached {
var postObject = PostCore.init(context : self.viewContext)
postObject.name = "Test"
var image = ImageCore.init(context: self.viewContext)
imageObject.data = image_data
postObject.image = imageObject
do {
if Thread.isMainThread {
print("Running on the main thread parent")
}else {
print("Other thread")
}
try self.viewContext.save()
print("Reference \(post_struct.ref) child saved")
}catch {
print("Error that produced \(post.ref) catched while trying to savethe data on child: \(error.localizedDescription),number : \(error)");
}
if post_struct.ref == "AAAA"{
var post = PostCore.fetchRequest()
var predicate = NSPredicate.init(format: "ref like %#", post.ref!)
fetchCheckIn.predicate = predicate
fetchCheckIn.fetchLimit = 1
fetchCheckIn.includesPropertyValues = true
let result = try! self.viewContext.fetch(fetchCheckIn)
print(result)
//This line returns the Object
print(self. checkPostExistHasPictures(refPost: post.ref))
//This is the line where always come (false, false) meaning that the Post is not saved
}
}
func getPostStructFromDB(refPost : String)async->PostStruct {
return await self.viewContext.perform{
var fetchPost = PostCore.fetchRequest()
var predicate = NSPredicate.init(format: "ref like %#", refPost)
fetchPost.predicate = predicate
fetchPost.fetchLimit = 1
fetchPost.includesSubentities = true
fetchPost.returnsObjectsAsFaults = false
fetchPost.includesPropertyValues = true
let result = try? self.viewContext.fetch(fetchCheckIn)
var refPost = result.first.ref
return PostStruct(ref : ref, image: refPost.image.data)
}
}
}
func checkPostExistHasPictures(refPost : String)->(Bool,Bool){
var fetchCheckIn = CheckInCore.fetchRequest()
var predicate = NSPredicate.init(format: "ref like %#", refPost)
fetchCheckIn.predicate = predicate
fetchCheckIn.fetchLimit = 1
var exist = false
var hasPicture = false
self.viewContext.performAndWait {
do{
let result = try? self.viewContext.fetch(fetchCheckIn)
if result?.first == nil {
}else {
print("Exists with reference \(reference_checkin)")
if result!.first!.pic_ref == nil {
exist = true
hasPicture = false
}else if result!.first!.image!.count != 0 {
exist = true
hasPicture = true
}
}
}catch {
print("error catched")
}
}
return(exist, hasPicture)
}
}
Relation between PostCore and ImageCore is zero to many.
I don't get any error code or error message. I commented the line where I get the error. I have tried all possible ways using a backGroundContext each time a save is made to not block the main thread and still is the same problem.
Your code is extremely confusing.
Why have you got Task scattered everywhere, you are doing no async/await work?
Your savePostStruct method takes a parameter called post which contains your data, then you immediately replace it with a value of the same name of type PostCore, which is presumably a managed object, then you only set one value on it.
Then, when you come to fetch an item, there's nothing there, because you haven't written anything to the managed object besides the name "Test".
At the very least, you have to change this line:
var post = PostCore.init(context : self.viewContext)
To
let postObject = PostCore(context: self.viewContext)
Then you won't get confused between the managed object and the struct you're passing in.
You are also saving the context before you've written any of the values to it.
I have to cache images one by one passing them into an array.
When I configure Controller, I got array of images from API. Images I got using Animation, every 0.1 sec I got new Image. But when I got all of them, I need to use cached images instead of load them again
Some variables for help
private var imagesCount = 0
private var nextImage = 0
private var imagesArray: [String] = []
private var isAnimating = true {
didSet {
if isAnimating {
animateImage()
}
}
}
Here I configure VC, and do imagesArray = images, then I will use only my array of urls
func configureView(images: ApiImages) {
guard let images = images.images else { return }
imagesArray = images
imagesCount = images.count
imageView.setImage(imageUrl: images[nextImage])
nextImage += 1
animateImage()
}
When I start my animation. Every 0.1 I get new one image, but after the end of cycle I want to use cached images
func animateImage() {
UIView.transition(with: self.imageView, duration: 0.1, options: .transitionCrossDissolve) { [weak self] in
guard let self = self else { return }
self.imageView.setImage(imageUrl: self.imagesArray[self.nextImage])
} completion: { [weak self] _ in
guard let self = self else { return }
if self.nextImage == self.imagesCount - 1{
//here is end of cycle
self.nextImage = 0
} else {
self.nextImage += 1
}
if self.isAnimating {
self.animateImage()
}
}
}
I use kingfisher, so what options I have to put here??
extension UIImageView {
func setImage(imageUrl: String, completion: (() -> ())? = nil) {
self.kf.setImage(with: URL(string: imageUrl), options: [.transition(.fade(0.5)), .alsoPrefetchToMemory]) { result in
completion?()
}
}
}
Thank you!
I know it sounds crazy, but just curious how I can reduce the if loop iteration for following? I have tried using guard let but stucked at some place.
{
if arenaEventItems == nil || arenaEventItems.count <= 0 {
return
}
if (arenaEventItems.count > 0 && (self.arenaEvents?.monthsDictObjList.count)! > 0){
if (self.tableView != nil){
if let arrVisibleRows = self.tableView.indexPathsForVisibleRows as? [IndexPath]{
if (self.tableView.indexPathsForVisibleRows!.count > 0){
let indexPath : IndexPath = self.tableView.indexPathsForVisibleRows!.first!
if let dict = self.arenaEvents?.monthsDictObjList[indexPath.row] {
if(self.arenaHeaderView != nil) && (dict.count) > 0 {
self.arenaHeaderView?.setMonthTitle(string: (dict.keys.first!))
let selectedMonthTitle = (dict.keys.first!)
for month in (self.arenaEvents?.uniqueMonthOnlyList)! {
if (selectedMonthTitle.contains(month)){
selectedMonthIndex = (self.arenaEvents?.uniqueMonthOnlyList.index(of: month)!)!
break
}
}
}
}
}
}
}
}
}
You can reduce it like that, without any forced unwrapping or nesting:
guard let arenaEventItems = arenaEventItems,
!arenaEventItems.isEmpty,
let arenaEvents = self.arenaEvents,
!arenaEvents.monthsDictObjList.isEmpty,
let arenaHeaderView = self.arenaHeaderView,
let indexPath = self.tableView?.indexPathsForVisibleRows?.first,
let selectedMonthTitle = arenaEvents.monthsDictObjList[indexPath.row].keys.first
else {
return
}
arenaHeaderView.setMonthTitle(string: selectedMonthTitle)
if let monthIndex = arenaEvents.uniqueMonthOnlyList.index(where: { selectedMonthTitle.contains($0) }) {
selectedMonthIndex = monthIndex
}
you replace if ... return with guard !... else return to avoid nesting
you replace .count > 0 with !...isEmpty as best practice
you replace multiple access to self.something? with if let something = self.something to avoid threading issues
you unloop for ... in ... { if (...) { ... } } to .index(where: ...)
You can combine all the conditions in "if" and get something like this:
if let eventItems = arenaEventItems,
eventItems.count > 0,
let events = self.arenaEvents,
!events.monthsDictObjList.isEmpty,
let tableView = self.tableView,
let arrVisibleRows = self.tableView.indexPathsForVisibleRows as? [IndexPath],
!arrVisibleRows.isEmpty,
let indexPath : IndexPath = arrVisibleRows.first,
let dict = events.monthsDictObjList[indexPath.row],
let headerView = self.arenaHeaderView,
!dict.isEmpty {
headerView.setMonthTitle(string: (dict.keys.first!))
let selectedMonthTitle = (dict.keys.first!)
for month in events.uniqueMonthOnlyList! {
if (selectedMonthTitle.contains(month)){
selectedMonthIndex = (events.uniqueMonthOnlyList.index(of: month)!)!
break
}
}
}
You should consider restructuring your code, your code is not readable and incomprehensible for anyone who look at it. Since, you are using Swift, it is really easy to write such code with guard ... else, if ... let
pattern.
Some improvements that you can do on class is have your view non nil ie make them implicitly unwrapped optional, since you will always be connecting them to storyboard.
#IBOutlet var tableView: UITableView!
#IBOutlet var arenaHeaderView: ArenaHeaderView!
Also, you have arrays which can go to nil, why do you want it to be nil. You could simply initialize an empty array and dictionaries. That way you can reduce some more comparison code like so,
arenaEventItems: [String: String] = [:]
With that changes and a bit of refactoring, you could possibly rewrite your code to something like this,
guard !arenaEventItems.isEmpty,
let arenaEvents = arenaEvents,
let indexPath = tableView.indexPathsForVisibleRows?.first,
let dict = arenaEvents.monthsDictObjList[indexPath.row],
let selectedMonthTitle = dict.keys.first
else {
return
}
arenaHeaderView.setMonthTitle(string: selectedMonthTitle)
for month in arenaEvents.uniqueMonthOnlyList where selectedMonthTitle.contains(month) {
if let selectedIndex = arenaEvents.uniqueMonthOnlyList.index(of: month) {
selectedMonthIndex = selectedIndex
break
}
}
I'm making an app with a login feature, in which you can post, edit, and delete your own favorite spots, and like the posts of other users.
I'm trying to implement an edit button, that only shows on the posts you posted yourself, and hidden on the posts from others.
I have my FeedViewController, in which I call the 'configureCell function' in the UITableviewCell class. This is a part of the code in the FeedViewController:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("PostCell") as? PostCell {
let postList = searchController.active ? searchResult[indexPath.row] : posts[indexPath.row]
let post = postList
cell.request?.cancel()
var image: UIImage?
if let url = post.postImgUrl {
image = FeedVC.imageCache.objectForKey(url) as? UIImage
}
var image2: UIImage?
if let url2 = post.userImgUrl {
image2 = FeedVC.imageCache.objectForKey(url2) as? UIImage
}
cell.configureCell(post, img: image, img2: image2)
return cell
} else {
return PostCell()
}
}
This is the code in my UITableviewCell (class PostCell: UITableViewCell):
func configureCell(post: Post, img: UIImage?, img2: UIImage?) {
self.post = post
likeRef = DataService.ds.REF_USER_CURRENT.childByAppendingPath("likes").childByAppendingPath(post.postKey)
self.descriptionText.text = post.postDescription
self.descriptionText.scrollRangeToVisible(NSMakeRange(0, 0))
self.likesLbl.text = "\(post.likes)"
self.postTitle.text = post.postTitle
self.postLocation.text = post.postLocation
self.username.text = post.username
self.postKeyLbl.text = post.key
if post.postImgUrl != nil {
if img != nil {
self.showcaseImg.image = img
} else {
request = Alamofire.request(.GET, post.postImgUrl!).validate(contentType: ["image/*"]).response(completionHandler: { request, response, data, err in
if err == nil {
let _img = UIImage(data: data!)!
self.showcaseImg.image = img
FeedVC.imageCache.setObject(_img, forKey: self.post.postImgUrl!)
} else {
print(err.debugDescription)
}
})
}
} else {
self.showcaseImg.hidden = true
}
if post.userImgUrl != nil {
if img2 != nil {
self.profileImg.image = img2
} else {
request = Alamofire.request(.GET, post.userImgUrl!).validate(contentType: ["image/*"]).response(completionHandler: { request, response, data, err in
if err == nil {
let _img2 = UIImage(data: data!)!
self.profileImg.image = img2
FeedVC.imageCache.setObject(_img2, forKey: self.post.userImgUrl!)
} else {
print(err.debugDescription)
}
})
}
} else {
print("no image")
}
likeRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
if snapshot.value is NSNull {
self.likesImg.image = UIImage(named: "heart")
} else {
self.likesImg.image = UIImage(named: "heart-filled")
}
})
let getUid = NSUserDefaults.standardUserDefaults().valueForKey(KEY_UID)
if String(getUid!) == (self.post.postUid) {
editBtn.hidden = false
delBtn.hidden = false
}
}
It's about the last part:
let getUid = NSUserDefaults.standardUserDefaults().valueForKey(KEY_UID)
if String(getUid!) == (self.post.postUid) {
editBtn.hidden = false
delBtn.hidden = false
}
That part isn't working. The edit (and delete) button are showing in the posts of the specific user, but also in some of the posts of other users. I don't know what I'm doing wrong. Maybe it's because I have also implemented a 'sorting data function', where I am sorting the posts on 'date' or 'likes'. When the posts reshuffle, more edit buttons appear on random cells (from other users).
But I really don't know. I hope someone is able to help me!? Let me know if you need some more code:-) Thanks a lot!
Kind regards,
Dide
Add an else clause to this:
if String(getUid!) == (self.post.postUid) {
editBtn.hidden = false
delBtn.hidden = false
} else {
editBtn.hidden = true
delBtn.hidden = true
}
Because you are reusing cells, some of the cells where the buttons may not be hidden because it is associated with a post of a certain user may be reused for another post of a different user. Since you don't have an else clause handling this situation, those buttons whose 'hidden' property were originally set to false will remain unhidden, even if the postUid and getUid do not match. Hope this helps!
For a String which have both String and Int values (one of each) is it possible to do simple sort that will give the items ordered in numerical order as the primary order and alphabetical as the secondary order
var nameArray = ["Dave7", "Bob8", "Cathy9", "Henry10", "Susan10", "Pat11", "Steve12", "Dan12", "Ken1", "Sean2", "Howard3", "Dixie3", "Newman5", "Billy6"]
var sortedNameArray = nameArray.sort { $0.compare($1, options: .NumericSearch) == .OrderedAscending }
print(sortedNameArray) // gives the following:
Don't want this -> ["Billy6", "Bob8", "Cathy9", "Dan12", "Dave7", "Dixie3", "Henry10", "Howard3", "Ken1", "Newman5", "Pat11", "Sean2", "Steve12", "Susan10"]
Even though .NumericSearch was used the result is alphabetical.
I was able to get the desired result using a custom binary tree. Which gives the results:
Ken1 Sean2 Dixie3 Howard3 Newman5 Billy6 Dave7 Bob8 Cathy9 Henry10 Susan10 Pat11 Dan12 Steve12
But is there a simpler solution?
extension String {
var integerValue: Int? {
return Int(self)
}
}
func extractValueFromString(theString:String)->Int{
var catNumber: [Character] = []
//print("theString \(theString)")
for character in theString.characters{
var characterString = String(character)
if var value = characterString.integerValue { //if we don't check program crashes
//if numberSet.contains(Int(String(character))!) { //another way to check but redundant here
catNumber.append(character)
//print(catNumber)
// }
}
}
let numberString = String(catNumber)
return Int(numberString)!
}
class Node{
//nodes now only arrange strings
var data = ""
var value = Int()
var left:Node?;
var right:Node?;
deinit {
//print("deleting \(data)")
// print("node deleted")
}
init(data:String){
self.data = data;
//print(data)
}
}
class binaryTreeSort{
var root:Node?
init(){
}
deinit {
//print("tree deleted")
}
func getRoot()->Node{
return root!
}
func insertNewValue(data:String){
let newNode = Node(data:data)
var node:Node? = root
if (node == nil){
root = newNode
}
while (node != nil) {
let currentValue = node?.data
if currentValue == ""{
node?.data = data
return
}
if currentValue == data {
//we don't want duplicates.
return
}
if extractValueFromString(currentValue!) < extractValueFromString(data) {
if (node!.right != nil) {
node = node!.right
//print("Going Right at data \(node!.data)")
}else{
node!.right = newNode
//print("Going New Right at data \(node!.data)")
return
}
}else if extractValueFromString(currentValue!) == extractValueFromString(data){
if currentValue < data {
if (node!.right != nil) {
node = node!.right
//print("Going Right at data \(node!.data)")
}else{
node!.right = newNode
//print("Going New Right at data \(node!.data)")
return
}
}else{
if (node!.left != nil) {
//print("Going Left at data \(node!.data)")
node = node!.left
}else{
node!.left = newNode
//print("Going New Left at data \(node!.data)")
return
}
}
}
else{
if (node!.left != nil) {
//print("Going Left at data \(node!.data)")
node = node!.left
}else{
node!.left = newNode
//print("Going New Left at data \(node!.data)")
return
}
}
}
}
func inorderPrint(baseNode:Node){
if(baseNode.left != nil)
{
inorderPrint(baseNode.left!);
//print(" \(baseNode.data)")
}
print("\(baseNode.data)")
if(baseNode.right != nil)
{
inorderPrint(baseNode.right!)
//print(" \(baseNode.data)")
}
}
func reverseOrderPrint(baseNode:Node){
if(baseNode.right != nil)
{
reverseOrderPrint(baseNode.right!)
//print(" \(baseNode.data)")
}
print("\(baseNode.data)")
if(baseNode.left != nil)
{
reverseOrderPrint(baseNode.left!);
//print(" \(baseNode.data)")
}
}
}
var myBinaryTreeSort:binaryTreeSort? = binaryTreeSort()
for item in nameArray{
//print(item)
myBinaryTreeSort!.insertNewValue(item)
}
myBinaryTreeSort!.inorderPrint(myBinaryTreeSort!.getRoot())
print("---------------")
myBinaryTreeSort!.reverseOrderPrint(myBinaryTreeSort!.getRoot())
myBinaryTreeSort = nil //delete the tree
Use map to split the names into parts, sort to sort by number and name, and then map to restore the original:
func splitName(name:String) -> (String, Int) {
if let range = name.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet()) {
return (name[name.startIndex..<range.startIndex], Int(name[range.startIndex..<name.endIndex])!)
} else {
return (name, 0)
}
}
print(nameArray.map(splitName).sort({ lhs, rhs in
if lhs.1 < rhs.1 {
return true
} else if lhs.1 > rhs.1 {
return false
} else {
return lhs.0 < rhs.0
}
}).map({ "\($0.0)\($0.1)" }))
Some other ways it could be done would be to maintain element 0 of the tuple as the full name (with numbers) and then the final map just becomes map({ $0.0 }) Depending on sizes, this may be more optimal than splitting the name each time it's compared.
If you have an array, you can sort with a custom closure.
For example:
nameArray.sort({extractValueFromString($0) < extractValueFromString($1)})
Will get you close. You just need to check if they are equal and return $0 < $1 instead.
Here's how I solved this, doing something similar to what #Lou-Franco alluded to:
func endInteger(word: String) -> Int {
if let range = word.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet()){
let numberSubstring = word.substringFromIndex(range.startIndex)
return Int(numberSubstring) ?? 0
}
return 0
}
let sortedArray = yourArray.sort{endInteger($1) > endInteger($0)}