Vote system using double tap - ios

I am trying to implement a vote system when users double tap the image. When the user double taps the image the vote count adds one and saves to parse. In my cell I have
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
//postsLabel.textAlignment = NSTextAlignment.Center
bottomBlurView.backgroundColor = UIColor(red: 0, green: 0.698, blue: 0.792, alpha: 0.75)
votesLabel!.textAlignment = NSTextAlignment.Right
let gesture = UITapGestureRecognizer(target: self, action: Selector("onDoubleTap:"))
gesture.numberOfTapsRequired = 2
contentView.addGestureRecognizer(gesture)
heartIcon?.hidden = true
//profileImageView.layer.cornerRadius = profileImageView.frame.height / 2
}
func onDoubleTap (sender: AnyObject) {
if self.complition != nil
{
self.complition!()
}
heartIcon?.hidden = false
heartIcon?.alpha = 1.0
UIView.animateWithDuration(1.0, delay:1.0, options:nil, animations: {
self.heartIcon?.alpha = 0
}, completion: {
(value:Bool) in
self.heartIcon?.hidden = true
})
}
}
and in my collection view controller I have
func onDoubleTap (indexPath:NSIndexPath)
{
let cell = self.collectionView.cellForItemAtIndexPath(indexPath) as! NewCollectionViewCell
let object = self.votes[indexPath.row]
if let likes = object["votes"] as? Int
{
object["votes"] = likes + 1
object.saveInBackgroundWithBlock{ (success:Bool,error:NSError?) -> Void in
println("Data saved")
}
cell.votesLabel?.text = "\(likes + 1)"
}
else
{
object["votes"] = 1
object.saveInBackgroundWithBlock{ (success:Bool,error:NSError?) -> Void in
println("Data saved")
}
cell.votesLabel?.text = "1"
}
}
However this does not add one and does not save to parse. Any help is appreciated. Thank you

You should place the first onDoubleTap in your collectionView's cellForItemAtIndexPath and configure it there.

Related

Reverse animation when button is pressed for second time in Table View

I know this has been asked before but did could not figure-out how to achieve this in the shortest possible way in my case.
When I click on "Add" button (see GIF below), the animation function animates "imageView" (in this case the galaxy image) to fly to cart i.e. "notificationButton". Also "Add" button changes to "Remove" and button color changes from black to red (see GIF below). Fine with that.
Now, when I click the button for the second time i.e deselect it i.e. make it to default state, everything reverses, but the image still flies to the cart !
Now, I want to reverse the animation flying imageView animation to its original position when I push the button back to its default position second time, and again original flying animation, if the button is pushed again as many times as I want.
Though I have added complete ProductViewController code here but you skip everything and look at the last extension ProductViewController
I know this most likely has two steps -
i) Identifying that "buttonHandlerAddToCart" button is pressed second time i.e. from selected/isselected step to default step.
ii) Reversing the animation function "func animation" in ProductViewController.
How to go about it?
Relevant code:
SSBadgeButton:-
import UIKit
class SSBadgeButton: UIButton {
var badgeLabel = UILabel()
var badge: String? {
didSet {
addBadgeToButon(badge: badge)
}
}
public var badgeBackgroundColor = UIColor.red {
didSet {
badgeLabel.backgroundColor = badgeBackgroundColor
}
}
public var badgeTextColor = UIColor.white {
didSet {
badgeLabel.textColor = badgeTextColor
}
}
public var badgeFont = UIFont.systemFont(ofSize: 12.0) {
didSet {
badgeLabel.font = badgeFont
}
}
public var badgeEdgeInsets: UIEdgeInsets? {
didSet {
addBadgeToButon(badge: badge)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
addBadgeToButon(badge: nil)
}
func addBadgeToButon(badge: String?) {
badgeLabel.text = badge
badgeLabel.textColor = badgeTextColor
badgeLabel.backgroundColor = badgeBackgroundColor
badgeLabel.font = badgeFont
badgeLabel.sizeToFit()
badgeLabel.textAlignment = .center
let badgeSize = badgeLabel.frame.size
let height = max(18, Double(badgeSize.height) + 5.0)
let width = max(height, Double(badgeSize.width) + 10.0)
var vertical: Double?, horizontal: Double?
if let badgeInset = self.badgeEdgeInsets {
vertical = Double(badgeInset.top) - Double(badgeInset.bottom)
horizontal = Double(badgeInset.left) - Double(badgeInset.right)
let x = (Double(bounds.size.width) - 10 + horizontal!)
let y = -(Double(badgeSize.height) / 2) - 10 + vertical!
badgeLabel.frame = CGRect(x: x, y: y, width: width, height: height)
} else {
let x = self.frame.width - CGFloat((width / 2.0))
let y = CGFloat(-(height / 2.0))
badgeLabel.frame = CGRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height))
}
badgeLabel.layer.cornerRadius = badgeLabel.frame.height/2
badgeLabel.layer.masksToBounds = true
addSubview(badgeLabel)
badgeLabel.isHidden = badge != nil ? false : true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addBadgeToButon(badge: nil)
fatalError("init(coder:) has not been implemented")
}
}
The ProductViewController code:
import UIKit
class ProductViewController: UIViewController, UITableViewDataSource,
UITableViewDelegate {
let notificationButton = SSBadgeButton()
let rightbarbuttonimage = UIImage(named:"ic_cart")
fileprivate var cart = Cart()
let scrollView = UIScrollView()
let sections = ["Section A", "Section B","Section C", "Section D","Section E","Section F","Section G","Section H", "Section I","Section J","Section K","Section L"]
let rowspersection = [2,3,1,2,2,3,3,1,4,2,1,2]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
self.tableView.backgroundColor = UIColor.gray
//Add and setup scroll view
self.tableView.addSubview(self.scrollView)
self.scrollView.translatesAutoresizingMaskIntoConstraints = false;
//Constrain scroll view
self.scrollView.leadingAnchor.constraint(equalTo: self.tableView.leadingAnchor, constant: 20).isActive = true;
self.scrollView.topAnchor.constraint(equalTo: self.tableView.topAnchor, constant: 20).isActive = true;
self.scrollView.trailingAnchor.constraint(equalTo: self.tableView.trailingAnchor, constant: -20).isActive = true;
self.scrollView.bottomAnchor.constraint(equalTo: self.tableView.bottomAnchor, constant: -20).isActive = true;
// customising rightBarButtonItems as notificationbutton
notificationButton.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
notificationButton.setImage(UIImage(named: "ic_cart")?.withRenderingMode(.alwaysTemplate), for: .normal)
notificationButton.badgeEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 15)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: notificationButton)
//following register is needed because I have rightbarbuttonitem customised as uibutton i.e. notificationbutton
notificationButton.addTarget(self, action: #selector(self.registerTapped(_:)), for: .touchUpInside)
}
#objc func registerTapped(_ sender: UIButton) {
self.performSegue(withIdentifier: "showCart", sender: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//Workaround to avoid the fadout the right bar button item
self.navigationItem.rightBarButtonItem?.isEnabled = false
self.navigationItem.rightBarButtonItem?.isEnabled = true
//Update cart if some items quantity is equal to 0 and reload the product table and right button bar item
cart.updateCart()
//self.navigationItem.rightBarButtonItem?.title = "Checkout (\(cart.items.count))"
notificationButton.badge = String(cart.items.count)// making badge equal to no.ofitems in cart
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// this segue to transfer data
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showCart" {
if let cartViewController = segue.destination as? CartViewController {
cartViewController.cart = self.cart
}
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return productMap.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productMap[section]?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let product = productMap[indexPath.section]![indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ProductTableViewCell") as! ProductTableViewCell
cell.imageView?.image = product.imagename
cell.delegate = self as CartDelegate
cell.setButton(state: self.cart.contains(product: product))
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch(section) {
case 0: return "Section A"
case 1: return "Section B"
case 2: return "Section C"
case 3: return "Section D"
case 4: return "Section E"
case 5: return "Section F"
case 6: return "Section G"
case 7: return "Section H"
case 8: return "Section I"
case 9: return "Section J"
case 10: return "Section K"
case 11: return "Section L"
default: return ""
}
}
}
extension ProductViewController: CartDelegate {
// MARK: - CartDelegate
func updateCart(cell: ProductTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let product = productMap[indexPath.section]![indexPath.row]
//Update Cart with product
cart.updateCart(with: product)
// self.navigationItem.rightBarButtonItem?.title = "Checkout (\(cart.items.count))"
notificationButton.badge = String(cart.items.count) // making badge equal to noofitems in cart
}
}
***// Most relevant code begins here -***
extension ProductViewController {
#IBAction func buttonHandlerAddToCart(_ sender: UIButton) {
let buttonPosition : CGPoint = sender.convert(sender.bounds.origin, to: self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)!
let cell = tableView.cellForRow(at: indexPath) as! ProductTableViewCell
let imageViewPosition : CGPoint = cell.imageView!.convert(cell.imageView!.bounds.origin, to: self.view)
let imgViewTemp = UIImageView(frame: CGRect(x: imageViewPosition.x, y: imageViewPosition.y, width: cell.imageView!.frame.size.width, height: cell.imageView!.frame.size.height))
imgViewTemp.image = cell.imageView!.image
animation(tempView: imgViewTemp)
}
func animation(tempView : UIView) {
self.view.addSubview(tempView)
UIView.animate(
withDuration: 1.0,
animations: {
tempView.animationZoom(scaleX: 1.5, y: 1.5)
}, completion: { _ in
UIView.animate(withDuration: 0.5, animations: {
tempView.animationZoom(scaleX: 0.2, y: 0.2)
tempView.animationRoted(angle: CGFloat(Double.pi))
tempView.frame.origin.x = self.notificationButton.frame.origin.x
tempView.frame.origin.y = self.notificationButton.frame.origin.y
}, completion: { _ in
tempView.removeFromSuperview()
UIView.animate(withDuration: 1.0, animations: {
self.notificationButton.animationZoom(scaleX: 1.4, y: 1.4)
}, completion: {_ in
self.notificationButton.animationZoom(scaleX: 1.0, y: 1.0)
})
})
}
)
}
}
extension UIView{
func animationZoom(scaleX: CGFloat, y: CGFloat) {
self.transform = CGAffineTransform(scaleX: scaleX, y: y)
}
func animationRoted(angle : CGFloat) {
self.transform = self.transform.rotated(by: angle)
}
}
I have also included ProductTableViewCell code, just in case:
import UIKit
protocol CartDelegate {
func updateCart(cell: ProductTableViewCell)
}
class ProductTableViewCell: UITableViewCell {
weak var myParent:ProductViewController?
#IBOutlet weak var imagename: UIImageView!
#IBOutlet weak var addToCartButton: UIButton!
var delegate: CartDelegate?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
addToCartButton.layer.cornerRadius = 5
addToCartButton.clipsToBounds = true
}
func setButton(state: Bool) {
addToCartButton.isUserInteractionEnabled = true
addToCartButton.isSelected = state
addToCartButton.backgroundColor = (!addToCartButton.isSelected) ? .blue : .red
}
#IBAction func addToCart(_ sender: Any) {
setButton(state: !addToCartButton.isSelected)
self.delegate?.updateCart(cell: self)
}
}
Edit: on #aheze's request :
struct Product: Equatable {
let imagename: UIImage
}
var productMap = [
0: [ Product(imagename:#imageLiteral(resourceName: "blue")), Product( imagename:#imageLiteral(resourceName: "CakeImage")) ]
1: [ Product(imagename:#imageLiteral(resourceName: "vectorlogo")), Product(imagename:#imageLiteral(resourceName: "PeasImge")), Product(imagename:#imageLiteral(resourceName: "castle"))],
2: [ Product( imagename:#imageLiteral(resourceName: "scoobydoo")),Product(imagename:#imageLiteral(resourceName: "ufo"))] ,
3: [ Product( imagename:#imageLiteral(resourceName: "wolfsky")),Product( imagename:#imageLiteral(resourceName: "universe")) ],
4: [ Product(imagename:#imageLiteral(resourceName: "werewolf")),Product( imagename:#imageLiteral(resourceName: "galaxy")) ]
]
Edit 2: class Cart, on #aheze's request:
import Foundation
class Cart {
var items : [CartItem] = []
}
extension Cart {
var totalQuantity : Int {
get { return items.reduce(0) { value, item in
value + item.quantity
}
}
}
func updateCart(with product: Product) {
if !self.contains(product: product) {
self.add(product: product)
} else {
self.remove(product: product)
}
}
func updateCart() {
for item in self.items {
if item.quantity == 0 {
updateCart(with: item.product)
}
}
}
func add(product: Product) {
let item = items.filter { $0.product == product }
if item.first != nil {
item.first!.quantity += 1
} else {
items.append(CartItem(product: product))
}
}
func remove(product: Product) {
guard let index = items.firstIndex(where: { $0.product == product }) else { return}
items.remove(at: index)
}
func contains(product: Product) -> Bool {
let item = items.filter { $0.product == product }
return item.first != nil
}
}
Further information you need, feel free...
Does this work? (the gif was too big)
https://imgur.com/a/jrcwEWv
I made separate functions for adding and removing from the cart.
extension ProductViewController: CartDelegate {
// MARK: - CartDelegate
func updateCart(cell: ProductTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let product = productMap[indexPath.section]![indexPath.row]
/// `var selectedIndexPaths = [IndexPath]()` defined inside `ProductViewController`, to keep track of the selected products
if selectedIndexPaths.contains(indexPath) {
if let index = selectedIndexPaths.firstIndex(of: indexPath) {
selectedIndexPaths.remove(at: index)
removeProductFromCart(indexPath: indexPath)
}
} else {
selectedIndexPaths.append(indexPath)
addProductToCart(indexPath: indexPath)
}
// addProductToCart(indexPath: indexPath)
/// **I commented this out because I don't have the code for `Cart`**
//Update Cart with product
// cart.updateCart(with: product)
// self.navigationItem.rightBarButtonItem?.title = "Checkout (\(cart.items.count))"
// notificationButton.badge = String(cart.items.count) // making badge equal to noofitems in cart
}
}
func addProductToCart(indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? ProductTableViewCell {
if let imageView = cell.imagename {
let initialImageViewFrame = imageView.convert(imageView.frame, to: self.view)
let targetImageViewFrame = self.notificationButton.frame
let imgViewTemp = UIImageView(frame: initialImageViewFrame)
imgViewTemp.clipsToBounds = true
imgViewTemp.contentMode = .scaleAspectFill
imgViewTemp.image = imageView.image
self.view.addSubview(imgViewTemp)
UIView.animate(withDuration: 1.0, animations: {
imgViewTemp.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}) { _ in
UIView.animate(withDuration: 0.5, animations: {
imgViewTemp.transform = CGAffineTransform(scaleX: 0.2, y: 0.2).rotated(by: CGFloat(Double.pi))
imgViewTemp.frame = targetImageViewFrame
}) { _ in
imgViewTemp.removeFromSuperview()
UIView.animate(withDuration: 1.0, animations: {
self.notificationButton.transform = CGAffineTransform(scaleX: 1.4, y: 1.4)
}, completion: {_ in
self.notificationButton.transform = CGAffineTransform.identity
})
}
}
}
}
}
func removeProductFromCart(indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? ProductTableViewCell {
if let imageView = cell.imagename {
let initialImageViewFrame = self.notificationButton.frame
let targetImageViewFrame = imageView.convert(imageView.frame, to: self.view)
let imgViewTemp = UIImageView(frame: initialImageViewFrame)
imgViewTemp.clipsToBounds = true
imgViewTemp.contentMode = .scaleAspectFill
imgViewTemp.image = imageView.image
self.view.addSubview(imgViewTemp)
var initialTransform = CGAffineTransform.identity
initialTransform = initialTransform.scaledBy(x: 0.2, y: 0.2)
initialTransform = initialTransform.rotated(by: CGFloat(Double.pi))
UIView.animate(withDuration: 0.5, animations: {
self.notificationButton.animationZoom(scaleX: 1.4, y: 1.4)
imgViewTemp.transform = initialTransform
}) { _ in
UIView.animate(withDuration: 1, animations: {
self.notificationButton.animationZoom(scaleX: 1, y: 1)
imgViewTemp.transform = CGAffineTransform.identity
imgViewTemp.frame = targetImageViewFrame
}) { _ in
imgViewTemp.removeFromSuperview()
}
}
}
}
}
Some things that you should fix:
Instead of using imagename (the image view that you added to your table view cell), you used cell.imageView! which is the built-in image view that all cells have. Don't use this.
Inside ProductTableViewCell, you should make a separate property for keeping track of selected/not selected state instead of using UIButton's isSelected. This way, you won't run into unwanted behavior when changing the button color (currently, a red rectangle will appear behind the button's text for a moment)
If you're combining transforms, you should do this:
var initialTransform = CGAffineTransform.identity
initialTransform = initialTransform.scaledBy(x: 0.2, y: 0.2)
initialTransform = initialTransform.rotated(by: CGFloat(Double.pi))
tempView.transform = initialTransform
instead of:
tempView.animationZoom(scaleX: 0.2, y: 0.2)
tempView.animationRoted(angle: CGFloat(Double.pi))
Here's the full project (added some more comments too).

Instantly update front end with new array value

I'm creating an app where users will send messages. I have the messenger working but the front end isn't updating instantly when a new comment is entered. You have to back out and then come back in to get it to show up.
I've researched examples and got the code from a course I took on udemy. I've tried with tableview.reload() and tableview.insertrows(). I'm sending the data using JSON to PHP and MySQL. It's showing in the db immediately but my code has it updating the UI first, it's just not working.
let messagetext = replyTxt.text.trimmingCharacters(in: .whitespacesAndNewlines)
hhmessages.insert(messagetext as AnyObject, at: hhmessages.endIndex)
let indexPath = IndexPath(row: self.hhmessages.count - 1, section: 0)
// update tableView
tableView.beginUpdates()
tableView.insertRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
// scroll to the bottom
tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
// empty textView and reload it
replyTxt.text = ""
textViewDidChange(replyTxt)
let recipient = messages["username"] as! String
let rid = String(describing: messages["recipient_id"]!)
let uuid = messages["uuid"] as! String
puuid = UUID().uuidString
// prepare request
let url = URL(string: "http://localhost/messagepost.php")!
let body = "sender_id=\(user_id)&sender=\(username)&text=\(messagetext)&recipient_id=\(rid)&recipient=\(recipient)&uuid=\(uuid)&puuid=\(puuid)"
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = body.data(using: .utf8)
The code should instantly update the UITableView with the latest comment and then commit to the database. I've been trying for two days with no luck. Any help is welcomed.
UPDATE WITH TABLE SOURCE CODE
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return hhmessages.count
}
// Return cell to display on the tableView
// How many sections. Means numberOfSections * rows = view
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let colorSmoothGray = UIColor(red: 229/255, green: 229/255, blue: 234/255, alpha: 1)
let colorBrandBlue = UIColor(red: 148 / 255, green: 33 / 255, blue: 147 / 255, alpha: 1)
let pictureURL = hhmessages[indexPath.row]["uploadpath"] as? String
// no picture in the post
if pictureURL?.isEmpty == true {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ConversationCell
cell.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi));
isLoading = true
let hhpost = hhmessages[indexPath.row]
let smimages = hhpost["path"] as? UIImage
let text = hhmessages[indexPath.row]["messagetext"] as! String
cell.messageLbl.text = text
cell.smavaImg.image = smimages
cell.messageLbl.textAlignment = .right
cell.messageLbl.backgroundColor = colorSmoothGray
cell.messageLbl.textColor = .black
cell.messageLbl.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
cell.messageLbl.font?.withSize(25)
cell.messageLbl.clipsToBounds = true
// get main queue to this block of code to communicate back
DispatchQueue.main.async {
cell.messageLbl.sizeToFit()
tableView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi)
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
}
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "PicCell", for: indexPath) as! PicConversationCell
cell.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi));
cell.smavaImg.image = smimages
//if let message = messageData {
for i in 0 ..< self.incoming.count {
// Confiture the constraints for cell
if self.incoming[indexPath.row] == 1 {
// Constraints
cell.lefBubbleConstraint.isActive = true
cell.rightBubbleConstraint.isActive = false
if cell.postpictureImg.image == nil {
cell.postpictureImg.backgroundColor = colorwhite
cell.postpictureImg.clipsToBounds = true
}
else {
cell.postpictureImg.backgroundColor = .clear
cell.postpictureImg.clipsToBounds = true
}
}
else if self.incoming[indexPath.row] == 0 {
// Constraints
cell.lefBubbleConstraint.isActive = false
cell.rightBubbleConstraint.isActive = true
if cell.postpictureImg.image == nil {
cell.postpictureImg.backgroundColor = colorwhite
cell.postpictureImg.clipsToBounds = true
}
else {
cell.postpictureImg.backgroundColor = .clear
cell.postpictureImg.clipsToBounds = true
}
}
// pictures logic
let pictureString = hhmessages[indexPath.row]["uploadpath"] as? String
let pictureURL = URL(string: pictureString!)!
// if there are still pictures to be loaded
if hhmessages.count != pictures.count {
URLSession(configuration: .default).dataTask(with: pictureURL) { (data, response, error) in
// downloaded
if let image = UIImage(data: data!) {
self.pictures.append(image)
DispatchQueue.main.async {
cell.postpictureImg.image = image
}
}
}.resume()
// cached picture
} else {
DispatchQueue.main.async {
cell.postpictureImg.image = self.pictures[indexPath.row]
}
}
}
return cell
}
}
Are you adding the element to what your numberOfRowsForSection calls? Before you call insertRows the data source needs to be update.

Swift 4: didSet #IBOutlet calls viewDidLayoutSubviews, but not with addSubview()

When selectCard is called, by commenting out various parts of the code I've discovered that viewDidLayoutSubviews is only called when score is updated in checkIfCardsMatch function. The score is only updated if we have matchedCards. Why is this happening?
I would like to have viewDidLayoutSubviews called every time selectCard is called. I noticed if I add CardTable.addSubview(grid) to my resetTable function than viewDidLayoutSubviews is called every time selectCard is called EXECPT when score is changed.
What is going on here and how can I make sure viewDidLayoutSubviews is called on each card selection?
// Score
#IBOutlet weak var scoreLabel: UILabel! {
didSet {
scoreLabel.text = "Score: \(score)"
}
}
private var score = 0 {
didSet {
scoreLabel.text = "Score: \(score)"
}
}
func selectCard(card: Card) {
// must deal more cards if we have a match first
if !matched.isEmpty && !game.cards.isEmpty {
print("must deal more cards if we have a match first")
resetTable()
return
}
// deal no longer possible
if !matched.isEmpty && game.cards.isEmpty {
print("deal no longer possible")
clearAndDeal()
}
// reset any mismatched card styles
if !misMatched.isEmpty {
print("reset any mismatched card styles")
resetMisMatchedStyle()
}
// select or deselect card
game.select(card: card)
if let idx = visibleCards.index(of: card){
visibleCards[idx].state = .selected
}
// check for match
checkIfCardsMatch()
// resetTable
resetTable()
}
private func resetTable(){
grid = CardTableView(frame: CardTable.bounds, cardsInPlay: visibleCards)
grid.delegate = self
}
private func checkIfCardsMatch(){
if let matchedCards = game.matchedCards() {
print("MATCHED!", matchedCards)
matched = matchedCards
game.clearSelectedCards()
score += 3
// set visible cards to matched
for card in matched {
if let idx = visibleCards.index(of: card){
visibleCards[idx].state = .matched
}
}
}else {
if game.selectedCards.count == 3 {
print("no match")
misMatched = game.selectedCards
game.clearSelectedCards()
score -= 5
for card in misMatched {
if let idx = visibleCards.index(of: card){
visibleCards[idx].state = .misMatched
}
}
}
}
}
The whole file:
//
// ViewController.swift
// Set
//
import UIKit
class ViewController: UIViewController, CardTableViewDelegate {
func delegateCardTap(card: Card){
print("called in view controller")
selectCard(card: card)
}
// Game
private var game = SetGame()
private lazy var grid = CardTableView(frame: CardTable.bounds, cardsInPlay: visibleCards)
// table to place all cards
#IBOutlet weak var CardTable: UIView! {
didSet {
// set up buttons with 12 cards
initalDeal()
}
}
#IBAction func newGame(_ sender: UIButton) {
score = 0
game = SetGame()
visibleCards.removeAll()
matched.removeAll()
misMatched.removeAll()
dealMoreButton.isEnabled = true
dealMoreButton.setTitleColor(#colorLiteral(red: 0.231372549, green: 0.6, blue: 0.9882352941, alpha: 1), for: .normal)
initalDeal()
grid.cards = visibleCards
}
override func viewDidLoad() {
grid.delegate = self
}
override func viewDidLayoutSubviews() {
print("viewDidLayoutSubviews")
super.viewDidLayoutSubviews()
// reset frame when device rotates
grid.frame = CardTable.bounds
// add cards to the card table
CardTable.addSubview(grid)
}
// Cards
private var visibleCards = [Card]()
// Score
#IBOutlet weak var scoreLabel: UILabel! {
didSet {
scoreLabel.text = "Score: \(score)"
}
}
private var score = 0 {
didSet {
scoreLabel.text = "Score: \(score)"
}
}
// Deal
#IBOutlet weak var dealMoreButton: UIButton!
#IBAction func deal(_ sender: UIButton) {
// have a match and cards available to deal
if !matched.isEmpty && !game.cards.isEmpty {
//TODO: fix this for new UI
clearAndDeal()
} else {
dealThreeMore()
}
// disable if we run out of cards
if game.cards.isEmpty {
disable(button: sender)
}
grid.cards = visibleCards
}
private func dealThreeMore(){
if visibleCards.count < game.cardTotal {
for _ in 0..<3 {
if let card = game.drawCard() {
// add more visible cards
visibleCards.append(card)
} else {
print("ran out of cards in the deck!")
}
}
}
}
private func disable(button sender: UIButton){
sender.isEnabled = false
sender.setTitleColor(#colorLiteral(red: 0.5, green: 0.5, blue: 0.5, alpha: 1), for: .normal)
}
private func clearAndDeal(){
print("in clearAndDeal")
//TODO: rewrite me
for card in matched {
if let index = visibleCards.index(of: card){
// remove matched styles
// draw new card
if let newCard = game.drawCard() {
// swap with old card
replace(old: index, with: newCard)
} else {
// ran out of cards in the deck!
hideButton(by: index)
}
}
}
matched.removeAll()
}
private var allCardsMatched: Bool {
let cards = visibleCards.filter({card in
// if let index = visibleCards.index(of: card){
//// return cardButtons[index].isEnabled
// }
return false
})
return cards.count == 3
}
private var misMatched = [Card]()
private var matched = [Card]()
func selectCard(card: Card) {
// must deal more cards if we have a match first
if !matched.isEmpty && !game.cards.isEmpty {
print("must deal more cards if we have a match first")
resetTable()
return
}
// deal no longer possible
if !matched.isEmpty && game.cards.isEmpty {
print("deal no longer possible")
clearAndDeal()
}
// reset any mismatched card styles
if !misMatched.isEmpty {
print("reset any mismatched card styles")
resetMisMatchedStyle()
}
// select or deselect card
game.select(card: card)
if let idx = visibleCards.index(of: card){
visibleCards[idx].state = .selected
}
// check for match
checkIfCardsMatch()
// resetTable
resetTable()
}
private func resetTable(){
grid = CardTableView(frame: CardTable.bounds, cardsInPlay: visibleCards)
grid.delegate = self
}
private func checkIfCardsMatch(){
if let matchedCards = game.matchedCards() {
print("MATCHED!", matchedCards)
matched = matchedCards
game.clearSelectedCards()
score += 3
// set visible cards to matched
for card in matched {
if let idx = visibleCards.index(of: card){
visibleCards[idx].state = .matched
}
}
}else {
if game.selectedCards.count == 3 {
print("no match")
misMatched = game.selectedCards
game.clearSelectedCards()
score -= 5
for card in misMatched {
if let idx = visibleCards.index(of: card){
visibleCards[idx].state = .misMatched
}
}
}
}
}
private func initalDeal(){
for _ in 0..<12 {
if let card = game.drawCard() {
visibleCards.append(card)
}
}
}
private func removeStyleFrom(button: UIButton){
button.layer.backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
}
private func resetMisMatchedStyle(){
for card in misMatched {
if let idx = visibleCards.index(of: card){
visibleCards[idx].state = nil
}
}
misMatched.removeAll()
}
private func replace(old index: Int, with newCard: Card){
visibleCards[index] = newCard
// style(a: cardButtons[index], by: newCard)
}
private func hideButton(by index: Int){
// let button = cardButtons[index]
// button.setAttributedTitle(NSAttributedString(string:""), for: .normal)
// button.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0)
// button.isEnabled = false
}
private func styleTouched(button: UIButton, by card: Card) {
if game.selectedCards.contains(card) {
button.layer.backgroundColor = #colorLiteral(red: 0.9848672538, green: 0.75109528, blue: 1, alpha: 1)
}else {
removeStyleFrom(button: button)
}
}
}

iOS, chart that loads in tableView cell somehow also loads in another cell?

So I have a tableView with a set of buttons in each cell. These are the options for a user to vote from.
When a user presses an option, a chart loads that shows the poll results.
However, when I scroll down, the chart appears inexplicably on another tableView cell. None of the buttons in that cell were ever pressed. Additionally, the chart loads in the second cell with the data of the first. I have been stuck on this for hours and have no idea why that is.
EDIT: Here is the code for the tableView
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "pollCell") as! PollCell
let titled = titles[indexPath.row]
cell.configure(polltitled: titled, num: indexPath.row)
return cell
}
And here are the methods for the Poll Cell:
var ref = FIRDatabase.database().reference()
var options = [String]()
var counts = [Int]()
self.loadedChart = false
#IBOutlet weak var chart: HorizontalBarChartView!
func configure(polltitled: String, num: Int) {
self.pollTitle.text = polltitled
self.row = num
self.runRemove() { (success) -> Void in
if success {
if (loadedChart == false) {
self.loadChart()
}
}
else {
}
}
}
func runRemove(completion:(_ success: Bool) -> Void) {
self.pollTitle.tag = 999
self.chart.tag = 998
for object in self.contentView.subviews {
if ((object.tag != 999) && (object.tag != 998)) {
object.removeFromSuperview()
}
}
completion(true)
}
func loadChart(){
ref.child("poll").child(StaticVariables.currentEventQR).child(self.pollTitle.text!).observe(.value) { (snapshot: FIRDataSnapshot!) in
self.counts.removeAll()
self.options.removeAll()
for item in snapshot.children {
let childSnapshot = snapshot.childSnapshot(forPath: (item as AnyObject).key)
let option = childSnapshot.key
self.options.append(option)
}
self.loadCounts()
self.createButtons()
}
}
func loadCounts() {
for i in self.options {
self.ref.child("poll").child(StaticVariables.currentEventQR).child(self.pollTitle.text!).child(i).observe(.value) { (snapshot: FIRDataSnapshot!) in
for item in snapshot.children {
let childSnapshot = snapshot.childSnapshot(forPath: (item as AnyObject).key)
let countsValue = childSnapshot.value as? NSDictionary
let count = countsValue?["Counter"] as! Int
self.counts.append(count)
}
}
}
}
func createButtons() {
var i = 1
var height = 0
if ((self.pollTitle.text?.characters.count)! > 37){
height = 22
}
for option in self.options{
let btn: UIButton = UIButton(frame: CGRect(x: Int(self.contentView.frame.size.width - 320)/2, y: 59+60*(i-1)+height, width: 320, height: 33))
if ((i)%2 == 1){
btn.backgroundColor = UIColor.init(red: 253/255, green: 185/255, blue: 39/255, alpha: 1.0)
} else {
btn.backgroundColor = UIColor.init(red: 0/255, green: 107/255, blue: 182/255, alpha: 1.0)
}
btn.addTarget(self, action: #selector(PollCell.optionOne(_:)), for: .touchUpInside)
btn.tag = i
self.contentView.addSubview(btn)
btn.setTitle(option, for: .normal)
i += 1
}
}
#IBAction func optionOne(_ sender: UIButton) {
self.loadedChart = true
delegate.refresh(self.row)
self.options.reverse()
self.counts.reverse()
self.setChart(dataPoints: self.options, values: self.counts)
ref.child("poll").child(StaticVariables.currentEventQR).child(pollTitle.text!).child((sender.titleLabel?.text!)!).observeSingleEvent(of: .value, with: { (snapshot) in
let item = snapshot.children.allObjects[0]
let childSnapshot = snapshot.childSnapshot(forPath: (item as AnyObject).key)
let responseID = childSnapshot.key
let reponseValue = childSnapshot.value as? NSDictionary
var response = reponseValue?["Counter"] as! Int
response += 1
self.ref.child("poll").child(StaticVariables.currentEventQR).child(self.pollTitle.text!).child((sender.titleLabel?.text!)!).child("count").setValue(["Counter": response])
})
self.contentView.bringSubview(toFront: self.chart)
}
func setChart(dataPoints: [String], values: [Int]) {
self.chartHeight.constant = CGFloat((RowHeightCounter.sharedInstance.counters[row])*62+39)
chart.chartDescription?.text = ""
chart.noDataText = ""
var dataEntries: [BarChartDataEntry] = []
for i in 0..<dataPoints.count {
let dataEntry = BarChartDataEntry(x: Double(i), y: Double(counts[i]))
dataEntries.append(dataEntry)
}
let chartDataSet = BarChartDataSet(values: dataEntries, label: "Votes")
let chartData = BarChartData(dataSet: chartDataSet)
chart.data = chartData
chart.xAxis.valueFormatter = IndexAxisValueFormatter(values: options)
chart.xAxis.granularity = 1
self.chart.alpha = 1.0
chart.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInBounce)
}
The issue is that UITableView reuses cells rather than creating new ones all the time. If the table had enough rows in it you would see that chart appear numerous times as you scrolled.
Once you add a chart to a cell it is there until removed so every time that cell gets used again the chart will be there.
UITableViewCell has a convient method that gets celled when it is about to be reused, prepareForReuse(). Implement this method and reset anything in the cell that needs to be reset before it appears again.
This does mean that you might need to rethink the interface between the cell and the data source. Maybe create a model object that holds the questions and result data along with wether the user has answered it or not. The data source could then hold an array of questions and pass the correct question to each cell. The cell could then configure itself based on the question
On a side note, I wouldn't remove the various views when changing from the question to the results. I would contain all the question stuff in one container view and all the chart stuff in another. Then I would set isHidden on the two containers as needed. This will keep your scrolling frame rate higher.

Edit actions in a table view similar to ios notifications center

I need to develop a tableview with edit actions on rows that look similar to the ones on iOS Notification center.
The resources I have used so far, say that I can only edit the background color and width of edit actions. What I want is the custom look like that of the notifications in iOS Notifications Center.
See the annotated portion. This is what I want.
This is what I have managed so far :|
Any help / guidance would be greatly appreciated!
Thanks in advance!
I ended up creating my own views as a part of the table view cell and adding custom animation..
Sample Code:
In my tableViewController:
override func viewDidLoad() {
super.viewDidLoad()
....
let tapGesture = UITapGestureRecognizer.init(target: self, action: #selector(handleTap))
tableView.addGestureRecognizer(tapGesture)
let leftSwipeGesture = UISwipeGestureRecognizer.init(target: self, action: #selector(handleLeftSwipe(_:)))
leftSwipeGesture.direction = .left
tableView.addGestureRecognizer(leftSwipeGesture)
let rightSwipeGesture = UISwipeGestureRecognizer.init(target: self, action: #selector(handleRightSwipe(_:)))
rightSwipeGesture.direction = .right
tableView.addGestureRecognizer(rightSwipeGesture)
}
func handleTap(_ gestureRecognizer: UISwipeGestureRecognizer) {
let point = gestureRecognizer.location(in: self.tableView)
if let indexPath = self.tableView.indexPathForRow(at: point) {
var shouldSelectRow = false
if indexPathBeingEdited != nil {
if indexPath == indexPathBeingEdited {
shouldSelectRow = true
} else {
//handle close of the cell being edited already
if let previousEditedCell = tableView.cellForRow(at: indexPathBeingEdited!) as? NotificationCenterTableViewCell {
previousEditedCell.closeEditActions()
indexPathBeingEdited = nil
}
}
} else {
shouldSelectRow = true
}
if shouldSelectRow {
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
tableView(tableView, didSelectRowAt: indexPath)
}
}
}
func handleLeftSwipe(_ gestureRecognizer: UISwipeGestureRecognizer) {
let point = gestureRecognizer.location(in: self.tableView)
if let indexPath = self.tableView.indexPathForRow(at: point) {
if indexPathBeingEdited != nil {
if indexPath == indexPathBeingEdited {
//Do nothing
} else {
//handle close of the cell being edited already
if let previousEditedCell = tableView.cellForRow(at: indexPathBeingEdited!) as? NotificationCenterTableViewCell {
previousEditedCell.closeEditActions()
indexPathBeingEdited = nil
}
}
}
//proceed with left swipe action
if let cell = tableView.cellForRow(at: indexPath) as? NotificationCenterTableViewCell {
cell.handleLeftSwipe(gestureRecognizer)
let notification = notificationsArray[indexPath.section].notificationItems[indexPath.row]
//Update the title of Read button
if notification.isNotificationRead {
cell.readUnreadButtonLabel.text = "Unread"
} else {
cell.readUnreadButtonLabel.text = "Read"
}
indexPathBeingEdited = indexPath
}
}
}
func handleRightSwipe(_ gestureRecognizer: UISwipeGestureRecognizer) {
let point = gestureRecognizer.location(in: self.tableView)
if let indexPath = self.tableView.indexPathForRow(at: point) {
if indexPathBeingEdited != nil {
if indexPath == indexPathBeingEdited {
if let cell = tableView.cellForRow(at: indexPath) as? NotificationCenterTableViewCell {
cell.closeEditActions()
indexPathBeingEdited = nil
//Update the title of Read button
cell.readUnreadButtonLabel.text = "Read"
}
} else {
//handle close of the cell being edited already
if let previousEditedCell = tableView.cellForRow(at: indexPathBeingEdited!) as? NotificationCenterTableViewCell {
previousEditedCell.closeEditActions()
indexPathBeingEdited = nil
//Update the title of Read button
previousEditedCell.readUnreadButtonLabel.text = "Read"
}
}
}
}
}
In my table view cell:
func handleLeftSwipe(_ gestureRecognizer: UISwipeGestureRecognizer) {
if !isBeingEdited {
//Action to open the edit buttons
UIView.animate(withDuration: 0.5, animations: {
self.notificationHolderViewLeadingConstraint.constant -= 248
self.notificationHolderViewTrailingConstraint.constant -= 248
self.editActionsView.isHidden = false
self.layoutIfNeeded()
}, completion: { (success) in
})
isBeingEdited = true
}
}
func closeEditActions() {
if isBeingEdited {
//Action to open the edit buttons
UIView.animate(withDuration: 0.5, animations: {
self.notificationHolderViewLeadingConstraint.constant += 248
self.notificationHolderViewTrailingConstraint.constant += 248
self.editActionsView.isHidden = true
self.layoutIfNeeded()
}, completion: { (success) in
})
isBeingEdited = false
}
}
override func draw(_ rect: CGRect) {
if let viewToRound = editActionsView {
let path = UIBezierPath(roundedRect:viewToRound.bounds,
byRoundingCorners:[.topRight, .topLeft, .bottomRight, .bottomLeft],
cornerRadii: CGSize(width: 20, height: 20))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
viewToRound.layer.mask = maskLayer
}
}
Just FYI, I have my edit buttons i.e., Read / View / Clear added to the editActionsView. The corresponding actions are hooked up with IBActions in my tableViewCell class.
The outcome is this:

Resources