I added a "favorite icon" (a heart) in the top Navigation Bar:
var faveMeItem = UIBarButtonItem (title: dua.isFavorite() ? "❤️" : "💔", style: .Plain, target: self, action: "toggleFav")
Is there a way to ensure it is changed (to a broken heart) as soon as it is tapped?
I have to go back to the tableView and come back to the detail, and then I see the updated icon. Tapping on it does the logic, but the heart is not updated. These are the functions in my Dua Class.
func removeFromFavorites() {
//retrieve all favorites
let favoriteDuaIds = NSUserDefaults.standardUserDefaults().objectForKey(Dua.favoriteDuasKey) as! [Int]?
if let favoriteDuaIds = favoriteDuaIds {
//iterate through all Duas and comapre their IDs
let newFavoriteDuaIds = favoriteDuaIds.filter { favoriteDuaId in
return favoriteDuaId != duaId
}
NSUserDefaults.standardUserDefaults().setObject(newFavoriteDuaIds, forKey: Dua.favoriteDuasKey)
}
}
func isFavorite() -> Bool {
//retrieve all favorites
let favoriteDuaIds = NSUserDefaults.standardUserDefaults().objectForKey(Dua.favoriteDuasKey) as! [Int]?
if let favoriteDuaIds = favoriteDuaIds {
//iterate through all Duas and comapre their IDs
for favoriteDuaId in favoriteDuaIds {
print (favoriteDuaId)
if favoriteDuaId == duaId {
return true
}
}
}
return false
}
func toggleFavorite() {
if isFavorite() {
removeFromFavorites()
} else {
addToFavorites()
}
}
class func favorites() -> [Dua] {
let favoriteDuaIds = NSUserDefaults.standardUserDefaults().objectForKey(favoriteDuasKey) as! [Int]?
if let favoriteDuaIds = favoriteDuaIds {
return DuasDataSource.duas.filter { dua in
return favoriteDuaIds.contains(dua.duaId)
}
} else {
return []
}
}
}
func toggleFavorite() {
if isFavorite() {
removeFromFavorites()
} else {
addToFavorites()
}
faveMeItem.title = dua.isFavorite() ? "❤️" : "💔" //Add this
}
Related
I have one toggle button. Initially, it will be in off mode. So when I turn on my toggle I need to pass a set of data to collection view. And when its tun off again I need to pass a set of data to collection view.
So here is my code :
#IBAction func cateSelTap(_ sender: Any) {
isChecked = !isChecked
if isChecked {
self.subsetTheCategoryListForClientLogin(isCityLogin: false)
}else {
self.subsetTheCategoryListForClientLogin(isCityLogin: true)
}
}
func subsetTheCategoryListForClientLogin(isCityLogin: Bool) {
let cityType = "city"
print("LOG:CATE1 \(self.categoryarr)")
var objectIds = [Int]()
var subsetArray = NSMutableArray()
for i in 0..<self.categoryarr.count {
let type = (self.categoryarr.object(at:i) as AnyObject).value(forKey: "type") as! String
if isCityLogin {
if type == cityType {
subsetArray.add(self.categoryarr.object(at:i) as AnyObject)
objectIds.append(i)
}
} else {
if type != cityType {
subsetArray.add(self.categoryarr.object(at:i) as AnyObject)
objectIds.append(i)
}
}
}
self.categoryarr.removeAllObjects()
self.categoryarr = subsetArray
print("LOG:CATE2 \(self.categoryarr)")
DispatchQueue.main.async{
self.categorycollection.reloadData()
// self.categorycollection.performSelector(onMainThread: #selector(self.categorycollection.reloadData), with: nil, waitUntilDone: true)
}
}
When I run my app, and when I turn off - my collection view data is not showing exact data..still it is showing previous data. Again when I turn off its fully blank.
Any help on this?
Add a new property like categoryarr, say categoryCollectionViewSource and assign initial data to it. Use categoryCollectionViewSource in collection view delegate methods instead of categoryarr. And in the filter method
func subsetTheCategoryListForClientLogin(isCityLogin: Bool) {
let cityType = "city"
print("LOG:CATE1 \(self.categoryarr)")
var objectIds = [Int]()
var subsetArray = NSMutableArray()
for i in 0..<self.categoryarr.count {
let type = (self.categoryarr.object(at:i) as AnyObject).value(forKey: "type") as! String
if isCityLogin {
if type == cityType {
subsetArray.add(self.categoryarr.object(at:i) as AnyObject)
objectIds.append(i)
}
} else {
if type != cityType {
subsetArray.add(self.categoryarr.object(at:i) as AnyObject)
objectIds.append(i)
}
}
}
//note this line
self.categoryCollectionViewSource.removeAllObjects()
self.categoryCollectionViewSource = subsetArray
print("LOG:CATE2 \(self.categoryarr)")
DispatchQueue.main.async{
self.categorycollection.reloadData()
}
}
#IBAction func cateSelTap(_ sender: Any) {
isChecked = !isChecked
if isChecked {
self.subsetTheCategoryListForClientLogin(isCityLogin: false)
} else {
self.subsetTheCategoryListForClientLogin(isCityLogin: true)
}
}
I have been using 'MessengerKit' for my chat section of my app. I can write and ready messages from firebase but when the chat updates, the messages duplicates sequentially(once, twice, thrice and so on). I am attaching my chatViewController Code below along with the message view on the app.
chatViewController Code :
class chatViewController: MSGMessengerViewController {
// Users in the chat
var nameOfHirer : String = ""
var nameofSeeker : String = ""
var seekerData = User(displayName: "", avatar: nil, isSender: false)
var hirerData = User(displayName: "", avatar: nil, isSender: true)
var id = 100
// Messages
lazy var messages: [[MSGMessage]] = []
func retrieveSeeker() {
let db = Firestore.firestore()
db.collection("Posts").document(jobID).collection("Applications").whereField("ID", isEqualTo: userID).getDocuments { (document, error) in
for document in document!.documents {
if error != nil {
}else {
let Name = document.get("Name") as! String
self.nameofSeeker = Name
let seeker = User(displayName: Name, avatar: nil, isSender: false)
self.seekerData = seeker
}
}
}
}
func retrieveHirer() {
let db = Firestore.firestore()
db.collection("Posts").document(jobID).getDocument { (document, error) in
if error != nil {
}else {
let Hirer = document?.get("Company Name") as! String
self.nameOfHirer = Hirer
let hirer = User(displayName: Hirer, avatar: nil, isSender: true)
self.hirerData = hirer
}
}
}
var uniqueID : String = ""
var messageBody : String = ""
var jobID : String = ""
var userID : String = ""
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
retrieveHirer()
retrieveSeeker()
// retrieveMessages()
print(messageBody)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = true
}
override var style: MSGMessengerStyle {
var style = MessengerKit.Styles.travamigos
style.inputPlaceholder = "Type your message here"
return style
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
collectionView.scrollToBottom(animated: false)
}
override func inputViewPrimaryActionTriggered(inputView: MSGInputView) {
id += 1
var newMessage : String = ""
let messageDictionary = ["Sender": userID, "MessageBody": inputView.message, "JobID": jobID]
// let body = MSGMessageBody.text(newMessage)
//
// let message = MSGMessage(id: id, body: body, user: hirerData, sentAt: Date())
inputView.resignFirstResponder()
let messageDB = Database.database().reference().child("Messages").child(jobID).child(userID)
messageDB.childByAutoId().setValue(messageDictionary) { (error, reference) in
if error != nil {
}else {
retrievemess()
}
}
func retrievemess() {
let messageDB = Database.database().reference().child("Messages").child(jobID).child(userID).queryLimited(toLast: 1)
messageDB.observe(.childAdded) { (snapshot) in
let value = snapshot.value as? [String: AnyObject]
let allmessage = value!["MessageBody"]
let body = MSGMessageBody.text(allmessage as! String)
let newmessage = MSGMessage(id: self.id, body: body, user: self.hirerData, sentAt: Date())
self.insert(newmessage)
}
}
}
override func insert(_ message: MSGMessage) {
collectionView.performBatchUpdates({
if let lastSection = self.messages.last, let lastMessage = lastSection.last, lastMessage.user.displayName == message.user.displayName {
self.messages[self.messages.count - 1].append(message)
let sectionIndex = self.messages.count - 1
let itemIndex = self.messages[sectionIndex].count - 1
self.collectionView.insertItems(at: [IndexPath(item: itemIndex, section: sectionIndex)])
} else {
print(messages.count)
self.messages.append([message])
let sectionIndex = self.messages.count - 1
self.collectionView.insertSections([sectionIndex])
}
}, completion: { (_) in
self.collectionView.scrollToBottom(animated: true)
self.collectionView.layoutTypingLabelIfNeeded()
})
}
override func insert(_ messages: [MSGMessage], callback: (() -> Void)? = nil) {
collectionView.performBatchUpdates({
for message in messages {
if let lastSection = self.messages.last, let lastMessage = lastSection.last, lastMessage.user.displayName == message.user.displayName {
self.messages[self.messages.count - 1].append(message)
let sectionIndex = self.messages.count - 1
let itemIndex = self.messages[sectionIndex].count - 1
self.collectionView.insertItems(at: [IndexPath(item: itemIndex, section: sectionIndex)])
} else {
self.messages.append([message])
let sectionIndex = self.messages.count - 1
self.collectionView.insertSections([sectionIndex])
}
}
}, completion: { (_) in
self.collectionView.scrollToBottom(animated: false)
self.collectionView.layoutTypingLabelIfNeeded()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
callback?()
}
})
}
}
// MARK: - MSGDataSource
extension chatViewController: MSGDataSource {
func numberOfSections() -> Int {
return messages.count
}
func numberOfMessages(in section: Int) -> Int {
return messages[section].count
}
func message(for indexPath: IndexPath) -> MSGMessage {
return messages[indexPath.section][indexPath.item]
}
func footerTitle(for section: Int) -> String? {
return "Just now"
}
func headerTitle(for section: Int) -> String? {
return messages[section].first?.user.displayName
}
}
// MARK: - MSGDelegate
extension chatViewController: MSGDelegate {
func linkTapped(url: URL) {
print("Link tapped:", url)
}
func avatarTapped(for user: MSGUser) {
print("Avatar tapped:", user)
}
func tapReceived(for message: MSGMessage) {
print("Tapped: ", message)
}
func longPressReceieved(for message: MSGMessage) {
print("Long press:", message)
}
func shouldDisplaySafari(for url: URL) -> Bool {
return true
}
func shouldOpen(url: URL) -> Bool {
return true
}
}
The pod I'm using is - https://github.com/steve228uk/MessengerKit
Screenshot:
Ok So I got the issue. The problem is that with
.queryLimited(toLast: 1).observe(.childAdded)
gives back multiple entries after first the first time its fired. The solution was to change .observe to .observeSingleEvent
I want to updated tableview from web services on change of segmented Control. As my code i have triggered event segmentedControlAction to update tableview in viewDidAppear event. it works on first time on Screen load but not working when i select next tab(Following), it fetch data from web service not update tableview. but when i switch to first Followers tab then come again to following tab it works, because it store data fetched from previuos web service
Datasource(ds) is get data from web service incase if it has nil records
TsButton is custom class which has only one member variable is object which is used for passing extra information to button click event
In StoryBoard there are two controls Segmented Control and tableview like in screen shot
My Code is below
class UserFollowController: BaseViewController
{
#IBOutlet var segmentedControl: UISegmentedControl!
#IBOutlet var tableView: UITableView!
private var ds : UserFollowTableDataSource!
override func viewDidLoad()
{
super.viewDidLoad()
ds = UserFollowTableDataSource(vc: self, tableView: tableView);
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if UserEntity.instance.username.characters.count == 0
{
let storyBoard : UIStoryboard = UIStoryboard(name:"Main", bundle:nil)
let vc = storyBoard.instantiateViewController(withIdentifier: "LoginController") as! LoginController
self.tabBarController?.selectedIndex = 0
self.present(vc, animated: true, completion: nil)
}
else
{
self.segmentedControlAction(sender: nil);
}
}
#IBAction func segmentedControlAction(sender: AnyObject?) {
if(segmentedControl.selectedSegmentIndex == 0)
{
ds.fetchFromWeb(type: "follower")
}
else
{
ds.fetchFromWeb(type: "following")
}
}
}
class UserFollowTableDataSource : NSObject, UITableViewDataSource
{
private var follower_records : [[String : Any]]?;
private var following_records : [[String : Any]]?;
private var records : [[String : Any]]?;
private var tableView : UITableView!;
private var vc : UserFollowController!;
private var loader : UIAlertController!;
private var type : String!
init(vc : UserFollowController, tableView : UITableView)
{
self.vc = vc;
self.tableView = tableView
self.loader = CommonUtil.getLoader(msg: "Getting List...");
super.init()
self.tableView.dataSource = self;
}
public func fetchFromWeb(type : String)
{
self.type = type;
self.loader.title = "Getting List...";
if (type == "follower")
{
if let data = self.follower_records
{
self.records = data;
self.tableView.reloadData();
self.tableView.setNeedsDisplay();
return;
}
self.vc.present(self.loader, animated: true, completion: nil)
HauteWebService.instance.get_user_followers(onSucess: { (response) in
if (response.status == 1)
{
DispatchQueue.main.async {
self.loader.dismiss(animated: true, completion: {
self.follower_records = response.data as! [[String : Any]]
self.records = self.follower_records
self.tableView.reloadData();
});
}
}
else
{
self._onError(msg: response.msg);
}
}, onFailure: { (msg) in
self._onError(msg: msg);
})
}
else
{
if let data = self.following_records
{
self.records = data;
self.tableView.reloadData();
self.tableView.setNeedsDisplay();
return;
}
self.vc.present(self.loader, animated: true, completion: nil)
HauteWebService.instance.get_user_following(onSucess: { (response) in
if (response.status == 1)
{
DispatchQueue.main.async {
self.loader.dismiss(animated: true, completion: {
self.following_records = response.data as! [[String : Any]]
self.records = self.follower_records
self.tableView.reloadData();
})
}
}
else
{
self._onError(msg: response.msg);
}
}, onFailure: { (msg) in
self._onError(msg: msg);
})
}
}
private func _onError(msg : String)
{
DispatchQueue.main.async {
self.loader.dismiss(animated: true, completion: {
let alert = CommonUtil.getAlertError(title: "Error while getting web service", msg: msg);
self.vc?.present(alert, animated: true, completion: nil)
})
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.records != nil
{
return self.records!.count;
}
return 0;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "FollowTableCell", for: indexPath) as! FollowTableCell
let index = indexPath.row;
cell.tag = index;
if let record = self.records![index] as? [String : Any]
{
let first = record["firstname"] as! String
let last = record["lastname"] as! String
cell.lblName.text = first + " " + last;
var add = record["city"] as! String;
let state = record["state"] as! String;
if state != ""
{
add = add + ", " + state
}
cell.lblAddress.text = add;
cell.btnFollow.backgroundColor = TFColor.hexToUIColor(hex: "#AAAAAA")
cell.btnFollow.setTitle("Follow", for: .normal)
cell.btnFollow.object = record;
if let v = record["is_followed"] as? Int
{
if (v == 1)
{
cell.btnFollow.backgroundColor = TFColor.hexToUIColor(hex: "#887474")
cell.btnFollow.setTitle("Following", for: .normal)
}
}
cell.btnFollow.addTarget(self, action: #selector(self.btnFollowClick), for: .touchUpInside)
cell.ratingStar.changeEvent = false;
if let v = Float(record["rating"] as! String)
{
cell.ratingStar.rating = Int(v)
}
else
{
cell.ratingStar.rating = 0;
}
cell.imgProfile.image = UIImage(named: "no_image");
if let url = record["profile_image_url"] as? String
{
if let img = ImageEntity.readCache(key: url)
{
cell.imgProfile.image = img
}
else
{
HauteWebService.instance.downloadFile(url: url, onSucess: { (data) in
if let img = UIImage(data: data)
{
ImageEntity.writeCache(key: url, image: img)
DispatchQueue.main.async {
if cell.tag == index
{
cell.imgProfile.image = img
}
}
}
}, onFailure: { (msg) in
})
}
}
}
return cell;
}
#objc func btnFollowClick(_ sender: TsButton)
{
let record = sender.object as! [String : Any];
let id = record["id"] as! String;
let data = ["user_id" : id]
if let v = record["is_followed"] as? Int
{
self.loader.title = "Please Wait...";
vc.present(self.loader, animated: true, completion: nil);
if (v == 1)
{
HauteWebService.instance.delete_user_follow(data: data, onSucess: { (response) in
if (response.status == 1)
{
DispatchQueue.main.async {
self.loader.dismiss(animated: false, completion: {
DispatchQueue.main.async {
self.follower_records = nil
self.following_records = nil
self.fetchFromWeb(type : self.type)
}
});
}
}
else
{
self._onError(msg: response.msg)
}
}, onFailure: { (msg) in
self._onError(msg: msg);
})
}
else
{
HauteWebService.instance.save_user_follow(data: data, onSucess: { (response) in
if (response.status == 1)
{
DispatchQueue.main.async {
self.loader.dismiss(animated: false, completion: {
DispatchQueue.main.async {
self.follower_records = nil
self.following_records = nil
self.fetchFromWeb(type : self.type)
}
});
}
}
else
{
self._onError(msg: response.msg)
}
}, onFailure: { (msg) in
self._onError(msg: msg);
})
}
}
}
}
class FollowTableCell : UITableViewCell
{
#IBOutlet var imgProfile : UIImageView!
#IBOutlet var actvityImgProfile : UIActivityIndicatorView!
#IBOutlet var lblName : UILabel!
#IBOutlet var lblAddress : UILabel!
#IBOutlet var btnFollow : TsButton!
#IBOutlet var ratingStar : TsRatingStar!
}
In your following webservice callback you have
self.following_records = response.data as! [[String : Any]]
self.records = self.follower_records
Thus you are setting the records of the tableView to the wrong values in the webservice callback.
It should be:
self.following_records = response.data as! [[String : Any]]
self.records = self.following_records
We have a function that when it finishes another function should be called in it's completion block but whatever is inside the completion block is never called. Here is the function:
func appendAllData (completion: () -> Void) {
guard let movieDetails = self.movieDetailsData else {
// handle nil case
return;
}
if let posterImage = self.movieDetailsData?.poster {
self.posterArray.append(posterImage)
}
if let overview = self.movieDetailsData?.overview {
self.overviewArray.append(overview)
}
if let releaseDate = self.movieDetailsData?.releaseData {
self.releaseInfoArray.append(releaseDate)
}
if let runtime = self.movieDetailsData?.runtime {
self.releaseInfoArray.append(String(describing: runtime))
}
if let genre = self.movieDetailsData?.genre {
if !genre.isEmpty {
self.releaseInfoArray.append(genre[0].name)
}
}
if let budget = self.movieDetailsData?.budget {
self.boxOfficeArray.append(budget)
}
if let revenue = self.movieDetailsData?.revenue {
self.boxOfficeArray.append(revenue)
}
if let homepage = self.movieDetailsData?.homepage {
self.homePageArray.append(homepage)
}
if let images = self.movieDetailsData?.images {
self.imageArray += images.backdropImages.map{ $0.filePath }
}
}
Here is how it's used:
self.appendAllData(completion: { _ in
//Nothing inside here gets called
DispatchQueue.main.async {
print(self.movieDetailsData?.poster )
if let bgImage = self.movieDetailsData?.poster {
self.backgroundImage.sd_setImage(with: URL(string:"\(baseImageURL)\(bgImage)"))
print("background pic loaded")
self.backgroundImage.addBlurEffect()
}
self.detailTableView.reloadData()
}
})
Nothing inside the completion block is called, any idea how to fix this?
I believe you need to call the completion() at the end for it execute your completion code.
func appendAllData (completion: () -> Void) {
guard let movieDetails = self.movieDetailsData else {
// handle nil case
return;
}
if let posterImage = self.movieDetailsData?.poster {
self.posterArray.append(posterImage)
}
if let overview = self.movieDetailsData?.overview {
self.overviewArray.append(overview)
}
if let releaseDate = self.movieDetailsData?.releaseData {
self.releaseInfoArray.append(releaseDate)
}
if let runtime = self.movieDetailsData?.runtime {
self.releaseInfoArray.append(String(describing: runtime))
}
if let genre = self.movieDetailsData?.genre {
if !genre.isEmpty {
self.releaseInfoArray.append(genre[0].name)
}
}
if let budget = self.movieDetailsData?.budget {
self.boxOfficeArray.append(budget)
}
if let revenue = self.movieDetailsData?.revenue {
self.boxOfficeArray.append(revenue)
}
if let homepage = self.movieDetailsData?.homepage {
self.homePageArray.append(homepage)
}
if let images = self.movieDetailsData?.images {
self.imageArray += images.backdropImages.map{ $0.filePath }
}
completion()
}
I need to run a function when the contacts have changed. If the application is active, you can do this with NotificationCenter as narrated in this post (sometimes It works when I add a new number to an existing contact). How do I know that the contact (or contacts) have been changed after the launch of the application?
I made the following functions for my task
#objc private func matchingContacts() {
if isSuccessContactUploading {
contactManager.matchingContacts(notMatch: { [weak self] in
guard let _self = self else { return }
debugPrint("matchingContacts != equals")
_self.isSuccessContactUploading = false
_self.syncContacts()
})
}
}
These functions are in ContactManager
func matchingContacts(notMatch: (() -> Void)?) {
getContacts { (contacts, error) in
if error == nil {
debugPrint("contacts count", contacts.count)
self.getContactsDictionaryFromCache(contacts, notMatch: {
notMatch?()
})
}
}
}
private func getContactsDictionaryFromCache(_ contacts: [CNContact], notMatch: (() -> Void)?) {
var isMatching = true
for contact in contacts {
let key = contact.identifier
do {
let cache = try Cache<NSDictionary>(name: "Contacts")
if let contactDictionary = cache[key] {
if !contactDictionary.isEqual(to: contact.dictionary) {
debugPrint("contactDictionary not matching")
isMatching = false
}
} else {
debugPrint("contactDictionary isn't here")
isMatching = false
}
} catch {
debugPrint(error.localizedDescription)
isMatching = false
}
}
if !isMatching {
notMatch?()
}
cacheContacts(contacts)
}
private func cacheContacts(_ contacts: [CNContact]) {
for contact in contacts {
let contactDictionary = contact.dictionary as NSDictionary
let key = contact.identifier
do {
let cache = try Cache<NSDictionary>(name: "Contacts")
cache[key] = contactDictionary
} catch {
debugPrint(error.localizedDescription)
}
}
}