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

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)
}
}
}

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).

cs193p Concentration: Emoji display card flip delayed for cards that were already displayed, after new game is pressed

I've successfully implemented a solution for the first assignment of Fall 2017 - Concentration.
Everything works fine, except that whenever you flip a card that was already displayed, after touching new game, there's a small lag/fade animation. This doesn't happen when you flip the card for the first time on the first game (see the video in the link below)
Problematic behaviour example
I thought this was a bug, but I checked other solutions and all of them have the same lag.
If anyone can give me a hint or knows why there is this lag, would really make me happy =)
Example solutions that have the same issue:
https://github.com/BestKora/Concentration-CS193P-Fall-2017
https://github.com/jamfly/cs193p-2017-fall-demo-and-solution
https://github.com/TiagoMaiaL/cs193p-assignments
My code for Concentration and ViewController:
class ViewController: UIViewController
{
private lazy var game = Concentration(numberOfPairsOfCards: numberOfPairsOfCards)
var numberOfPairsOfCards: Int {
return (cardButtons.count + 1) / 2
}
private let emojiLibrary = ["😀☚ïļðŸ˜˜ðŸĪŠðŸ˜žðŸ˜‡ðŸ˜ŽðŸ˜†ðŸ˜…😭ðŸ˜ģ😠",
"🎃ðŸ‘ŧðŸ˜ˆðŸ‘―ðŸ‘đðŸĪĄðŸ­ðŸ”Ū💉⛓💊ðŸ•Ŋ",
"🚗🚕🚙🚌🚎🏎🚓🚑🚒🚐🚚🚜",
"ðŸķðŸąðŸ­ðŸđ🐰ðŸĶŠðŸŧ🐞ðŸĻðŸŊðŸĶðŸŪ",
"âš―ïļðŸ€ðŸˆâšūïļðŸŽūðŸðŸ‰ðŸŽąðŸ“ðŸ’â›ģïļðŸĨŠ",
"👏ðŸĪðŸ‘ðŸ‘ŽðŸ‘ŠâœŠðŸĪžðŸĪŸðŸ‘ŒðŸ–ðŸĪ™â˜ïļ"]
private lazy var theme = emojiLibrary[emojiLibrary.count.arc4random]
#IBAction func newGame(_ sender: UIButton) {
// Create new game struct, initialize everything Model
game = Concentration(numberOfPairsOfCards: numberOfPairsOfCards)
// Initialize everything view
gameOverLabel.isHidden = true
emoji.removeAll()
theme = emojiLibrary[emojiLibrary.count.arc4random]
updateViewFromModel(touchedCard: 0)
}
#IBOutlet private weak var flipCountLabel: UILabel!
#IBOutlet private var cardButtons: [UIButton]!
#IBOutlet weak var gameOverLabel: UILabel!
#IBOutlet weak var scoreLabel: UILabel!
#IBAction private func touchCard(_ sender: UIButton) {
if let cardNumber = cardButtons.index(of: sender) {
game.chooseCard(at: cardNumber)
updateCardDisplay(index: cardNumber)
updateViewFromModel(touchedCard: cardNumber)
if game.gameOver {
gameOverLabel.isHidden = false
}
} else {
print("chosen card was not in cardButtons!")
}
}
private func updateCardDisplay(index:Int)
{
let card = game.cards[index]
let button = cardButtons[index]
if card.isFaceUp {
button.setTitle(getEmoji(for: card), for: UIControl.State.normal)
button.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
} else {
button.setTitle("", for: UIControl.State.normal)
button.backgroundColor = card.isMatched ? #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0) : #colorLiteral(red: 1, green: 0.5763723254, blue: 0, alpha: 1)
}
}
private func updateViewFromModel(touchedCard:Int) {
for index in cardButtons.indices {
if index != touchedCard {
let button = cardButtons[index]
let card = game.cards[index]
if !card.isFaceUp {
button.setTitle("", for: UIControl.State.normal)
button.backgroundColor = card.isMatched ? #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0) : #colorLiteral(red: 1, green: 0.5763723254, blue: 0, alpha: 1)
}
}
}
scoreLabel.text = String("Score: \(game.score)")
flipCountLabel.text = String("Flips: \(game.flipCount)")
}
private var emoji = [Card:String]()
private func getEmoji(for card: Card) -> String {
if emoji[card] == nil, theme.count > 0 {
let randomStringIndex = theme.index(theme.startIndex, offsetBy: theme.count.arc4random)
emoji[card] = String(theme.remove(at: randomStringIndex))
}
return emoji[card] ?? "?"
}
}
extension Int {
var arc4random: Int {
if self > 0 {
return Int(arc4random_uniform(UInt32(self)))
} else if self < 0 {
return -Int(arc4random_uniform(UInt32(abs(self))))
} else {
return 0
}
}
}
struct Concentration
{
private(set) var cards: [Card]
private(set) var gameOver = false
private(set) var flipCount = 0
private(set) var score = 0
private(set) var seenCards: Set<Card>
private(set) var indexOfOneAndOnlyFaceUpCard: Int? {
get {
return cards.indices.filter({cards[$0].isFaceUp}).oneAndOnly
}
set {
for index in cards.indices {
cards[index].isFaceUp = (index == newValue)
}
}
}
private var numberOfPlayingCards: Int {
get {
return cards.count - cards.indices.filter({cards[$0].isMatched}).count
}
}
mutating func chooseCard(at index: Int) {
assert(cards.indices.contains(index), "Concentration.chooseCard(at: \(index)): chosen index not in the cards")
if indexOfOneAndOnlyFaceUpCard != index && !cards[index].isMatched {
flipCount += 1
}
if !cards[index].isMatched {
if let matchIndex = indexOfOneAndOnlyFaceUpCard, matchIndex != index {
// check if cards match
if cards[matchIndex] == cards[index] {
cards[matchIndex].isMatched = true
cards[index].isMatched = true
score += 2
} else {
// mismatch of cards
if seenCards.contains(cards[index]) {
score -= 1
} else {
seenCards.insert(cards[index])
}
if seenCards.contains(cards[matchIndex]) {
score -= 1
} else {
seenCards.insert(cards[matchIndex])
}
}
if numberOfPlayingCards == 0 {
gameOver = true
cards[matchIndex].isFaceUp = false
} else {
cards[index].isFaceUp = true
}
} else {
indexOfOneAndOnlyFaceUpCard = index
}
}
}
init(numberOfPairsOfCards: Int) {
assert(numberOfPairsOfCards > 0, "Concentration.init(\(numberOfPairsOfCards)): you must have at least one pair of cards")
cards = [Card]()
for _ in 1...numberOfPairsOfCards {
let card = Card()
cards += [card, card]
}
cards.shuffle()
seenCards = Set<Card>()
}
}
extension Collection {
var oneAndOnly: Element? {
return count == 1 ? first : nil
}
}
All code was based in the demo, so should be familiar to someone that did the cs193p course/assignments.
Thanks

How to make different drawed views out of one UIView Class?

im making a iOS app/game that is like the game "Set".
for that I need to make multiple views so I made this class:
class CardSubview: UIView {
private let partsOfSpace:CGFloat = 12
private let occurenceOfForms: CGFloat = 3
private let color1:UIColor = someColor1
private let color2:UIColor = someColor2
private let color3:UIColor = somecolor3
private let attributeIdentifiers = [1,2,3]
private var openCardUp = false
public var isSelceted = false {
didSet {
if isSelceted == true {
self.backgroundColor = #colorLiteral(red: 0.5791940689, green: 0.1280144453, blue: 0.5726861358, alpha: 0.52734375)
self.layer.borderWidth = 5.0
self.layer.borderColor = #colorLiteral(red: 0.7450980544, green: 0.1568627506, blue: 0.07450980693, alpha: 1)
}
else {
self.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
self.layer.cornerRadius = 0
self.layer.borderWidth = 0
}
}
}
public func makePath() {
openCardUp = true
let path = coloreAndFill(path: self.occurenceOfForm(form: **index1**, occurence: **index2**, viewWidth: self.bounds.width, viewHeigth: self.bounds.height), chosenColor:**index3**, fillIdentifier: **index4**)
path.stroke()
}
override func draw(_ rect: CGRect) {
if openCardUp == true {
makePath()
}
}
....
so all you need to know that this makes an View with an rectangle or triangle or circle etc etc...
now I want to put 80 different of them into the CardBoardView
this is my CardBoardView
import UIKit
#IBDesignable
class CardBoardView: UIView {
static var index1 = 1
static var index2 = 1
static var index3 = 1
static var index4 = 3
public var cells = 12
let values = Values()
var card: CardSubview = CardSubview() {
didSet {
let tabRecognizer = UITapGestureRecognizer(target: self, action: #selector(tab))
card.addGestureRecognizer(tabRecognizer)
}
}
struct Values {
public let ratio:CGFloat = 2.0
public let insetByX:CGFloat = 3.0
public let insetByY:CGFloat = 3.0
public let allAvailableCards = 81
}
#objc private func tab (recognizer: UITapGestureRecognizer) {
switch recognizer.state {
case .ended:
card.isSelceted = !card.isSelceted
default: break
}
}
override func layoutSubviews() {
super.layoutSubviews()
var grid = Grid(layout: .aspectRatio(values.ratio), frame: self.bounds)
grid.cellCount = 12
for index in 0..<12 {
card = CardSubview(frame: grid[index]!.insetBy(dx: values.insetByX, dy: values.insetByY))
card.makePath()
addSubview(card)
}
}
}
so if I change one of the static indexes the drawing in the CardSubview will change.
thats my idea but it doesn't work because every time I change the index every card will get changed and draws the new form not only one.
how would you do that can anybody give me some thoughts to my code and some tipps?
There's no need for your card property in CardBoardView. You are creating a whole set of CardSubview instances so it makes no sense to have a property that only stores the last one.
You need to make several changes to your code.
Completely remove your card property and its didSet block.
Put the creation of the tap gesture in the loop that creates each CardSubview instance.
Use a local variable in the loop.
Update the tab method to get the card view from the gesture recognizer.
Here's the updated code:
class CardBoardView: UIView {
static var index1 = 1
static var index2 = 1
static var index3 = 1
static var index4 = 3
public var cells = 12
let values = Values()
struct Values {
public let ratio:CGFloat = 2.0
public let insetByX:CGFloat = 3.0
public let insetByY:CGFloat = 3.0
public let allAvailableCards = 81
}
#objc private func tab (recognizer: UITapGestureRecognizer) {
if recognizer.state == .ended {
if let card = recognizer.view as? CardBoardView {
card.isSelected = !card.isSelected
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
var grid = Grid(layout: .aspectRatio(values.ratio), frame: self.bounds)
grid.cellCount = 12
for index in 0..<12 {
let card = CardSubview(frame: grid[index]!.insetBy(dx: values.insetByX, dy: values.insetByY))
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tab))
card.addGestureRecognizer(tapRecognizer)
card.makePath()
addSubview(card)
}
}
}
It's possible that you may need to access each of the CardBoardView instances through some other code you haven't posted. So you may need a property to store all of the cards.
var cards = [CardBoardView]()
Then in the loop that creates each card, add:
cards.append(card)

Trouble getting chart object to work in custom UIView

I'm using Daniel Gindi's Charts library (iOS-charts), and in order to keep things simple I'm using a custom UIView to store the chart view (as a ChartViewBase). I have a class hierarchy of View Controller classes where the leaf class has the reference to the UIView with the ChartViewBase in it. The problem is that the chart is refusing to display, all I get is a black rectangle where the chart is supposed to be. I guess the code would probably be the most useful, so here goes nothing:
The generic UIView chart-holding class:
import UIKit
import Charts
protocol GenericChartViewDelegate: class {
func backButtonTapped()
func switchChartButtonTapped()
func finalizeResultsButtonTapped()
}
class GenericChartView: UIView, ChartViewDelegate
{
#IBOutlet weak var headerDisplay: UIButton!
#IBOutlet var view: UIView!
weak var delegate: GenericChartViewDelegate?
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
Bundle.main.loadNibNamed("GenericChartView", owner: self, options: nil)
self.addSubview(self.view)
chart.delegate = self
}
override init(frame: CGRect)
{
super.init(frame: frame)
Bundle.main.loadNibNamed("GenericChartView", owner: self, options: nil)
self.addSubview(self.view)
chart.delegate = self
}
#IBOutlet var chart: ChartViewBase!
#IBAction func BackButton(_ sender: UIButton) {
delegate?.backButtonTapped()
}
#IBAction func SwitchChartButton(_ sender: UIButton) {
delegate?.switchChartButtonTapped()
}
#IBAction func FinalizeResultsButton(_ sender: UIButton) {
delegate?.finalizeResultsButtonTapped()
}
func setHeader(header: String)
{
headerDisplay.setTitle(header, for: UIControlState.normal)
headerDisplay.layer.cornerRadius = MyVariables.borderCurvature
headerDisplay.titleLabel?.adjustsFontSizeToFitWidth = true
}
}
Now the base chart view controller:
class ChartViewControllerBase: UIViewController
{
var myColors : [NSUIColor] = []
//var bounds : CGRect!
override func viewDidLoad() {
super.viewDidLoad()
let sessionCount = ShareData.sharedInstance.currentSessionNumber
//NotificationCenter.default.addObserver(self, selector: "rotated", name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
createChart()
var myBase = getChartObject()
var myChartView = getChartDisplayView()
setDelegate()
if let myName = ShareData.sharedInstance.currentAccount.name {//sessionName.characters.last!
let myTitle = "Session " + "\(sessionCount)" + " Results - " + myName
myChartView.setHeader(header: myTitle)
}
//chartOriginalBounds = chart.bounds
if let resultChoice = ShareData.sharedInstance.sessionDataObjectContainer[ShareData.sharedInstance.currentAccountSessionNames[sessionCount - 1]]?.chartInfo
{
makeChart(resultChoice: resultChoice)
}
// Do any additional setup after loading the view.
}
func setDelegate()
{
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func createChart()
{
}
func getChartDisplayView() -> GenericChartView
{
return GenericChartView(frame: self.view.bounds)
}
func getChartObject() -> ChartViewBase
{
return ChartViewBase()
}
func makeChart(resultChoice: chartData)
{
}
func getColors(value: Double)
{
}
func getChartDataEntry(xval: Double, yval: Double) -> ChartDataEntry
{
return ChartDataEntry(x: xval, y: yval)
}
}
Now the LineAndBarChart code:
class LineAndBarChartViewControllerBase: ChartViewControllerBase {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func getChartObject() -> ChartViewBase
{
return ChartViewBase()
}
var yAxis: YAxis!
func setYAxisFormatter() {
}
func createSet(myEntries: [ChartDataEntry])
{
}
override func makeChart(resultChoice: chartData)
{
let chart = getChartObject() as! BarLineChartViewBase
chart.chartDescription?.enabled = false
chart.drawGridBackgroundEnabled = false
chart.dragEnabled = true
chart.setScaleEnabled(true)
chart.pinchZoomEnabled = true
let xAxis = chart.xAxis
xAxis.labelPosition = XAxis.LabelPosition.bottom
chart.rightAxis.enabled = false
xAxis.drawGridLinesEnabled = false
xAxis.drawAxisLineEnabled = true
xAxis.granularity = 1.0
//xAxis.setValuesForKeys(<#T##keyedValues: [String : Any]##[String : Any]#>)
yAxis = chart.leftAxis
chart.rightAxis.enabled = false
chart.legend.enabled = true
yAxis.granularity = 1.0
yAxis.drawGridLinesEnabled = false
var counter = 1.0
var myEntries : [ChartDataEntry] = []
var myXValues : [String] = []
if !resultChoice.plotPoints.isEmpty
{
for var item in resultChoice.plotPoints
{
let timeString : String =
{
let formatter = DateFormatter()
formatter.dateFormat = "h:mm a"
formatter.amSymbol = "AM"
formatter.pmSymbol = "PM"
return formatter.string(from: item.xValue)
}()
myXValues.append(timeString)
/*if(item.showXValue)
{
myXValues.append(timeString)
}
else
{
myXValues.append("")
}*/
let myEntry = getChartDataEntry(xval: counter, yval: Double(item.yValue))
getColors(value: myEntry.y)
counter += 1
myEntries.append(myEntry)
}
let myFormatter = MyXAxisValueFormatter(values: myXValues)
xAxis.valueFormatter = myFormatter
setYAxisFormatter()
createSet(myEntries: myEntries)
}
}
}
class MyXAxisValueFormatter: NSObject, IAxisValueFormatter {
var mValues: [String]
init(values: [String]) {
self.mValues = values;
}
func stringForValue( _ value: Double, axis: AxisBase?) -> String
{
// "value" represents the position of the label on the axis (x or y)
let myVal: Int = Int(value)
if (myVal < mValues.count)
{
return mValues[myVal];
}
else
{
return "ERROR"
}
}
}
Then the bar chart base code:
class BarChartViewControllerBase: LineAndBarChartViewControllerBase {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func createChart()
{
var myChart = getChartDisplayView()
myChart.chart = BarChartView(frame: myChart.chart.contentRect)
}
override func createSet(myEntries: [ChartDataEntry])
{
let chart = getChartObject() as! BarChartView
let mySet = BarChartDataSet(values: myEntries, label: "Values")
mySet.colors = myColors
mySet.valueColors = myColors
setValueFormatter(set: mySet)
let myBarChartData = BarChartData(dataSet: mySet)
myBarChartData.barWidth = 0.8
chart.data = myBarChartData
}
func setValueFormatter(set: BarChartDataSet)
{
}
override func getChartDataEntry(xval: Double, yval: Double) -> ChartDataEntry
{
return BarChartDataEntry(x: xval, y: yval)
}
}
And finally, the leaf (most derived class) code:
class DerivedClassChartViewController: BarChartViewControllerBase, GenericChartViewDelegate
{
#IBOutlet weak var chartView: GenericChartView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func getChartDisplayView() -> GenericChartView
{
return chartView
}
override func getChartObject() -> ChartViewBase
{
return chartView.chart
}
override func getColors(value: Double)
{
if (value == 0.0)
{
myColors.append(UIColor.black)
}
else if (value == 1.0)
{
myColors.append(MyVariables.colorOne)
}
else if (value == 2.0)
{
myColors.append(MyVariables.colorTwo)
}
else if (value == 3.0)
{
myColors.append(MyVariables.colorThree)
}
else if (value == 4.0)
{
myColors.append(MyVariables.colorFour)
}
else if (value == 5.0)
{
myColors.append(MyVariables.colorFive)
}
}
func backButtonTapped()
{
self.navigationController?.popViewController(animated: false)
}
func switchChartButtonTapped()
{
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertControllerStyle.actionSheet)
let goToLineChartAction = UIAlertAction(title: "Line Chart", style: UIAlertActionStyle.default)
{ (alertAction: UIAlertAction!) -> Void in
self.navigationController?.popViewController(animated: false)
let viewControllerStoryboardId = "LineChartDisplayViewController"
let storyboardName = "Main"
let storyboard = UIStoryboard(name: storyboardName, bundle: Bundle.main)
let viewController = storyboard.instantiateViewController(withIdentifier: viewControllerStoryboardId)
self.navigationController?.pushViewController(viewController, animated: false)
}
alertController.addAction(goToLineChartAction)
alertController.view.tintColor = UIColor.black
present(alertController, animated: true, completion: nil)
let presentationController = alertController.popoverPresentationController
presentationController?.sourceView = self.view
presentationController?.sourceRect = CGRect(x: 330, y: 210, width: 330, height: 210)
}
func finalizeResultsButtonTapped()
{
}
override func setDelegate()
{
chartView.delegate = self
}
override func setValueFormatter(set: BarChartDataSet)
{
set.valueFormatter = DerivedClassNumberFormatter()
}
}
class DerivedClassNumberFormatter: NSObject, IValueFormatter {
func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
var returnVal = "\(Int(value))"
return returnVal
}
}
I do have a few questions. Do you make the UIView the ChartViewDelegate, or should that be the view controller that holds the UIView that holds the chart object? Am I missing anything obvious? I've used custom UIViews before, so I know that that code is correct (aside from whether the class should be ChartViewDelegate or not, that is- that I don't know). Yes I do need all of these classes, as I will have many, many chart types in the future. Anyhow, any suggestions on what I can do to get the chart to display would be great.
Thanks,
Sean
I also am using the Charts library. I am not sure if I quite understand your limitation, but I was able to include Charts inside of a custom UIView. I have other methods (not shown) to pass actual data to the Labels and Charts from the View Controller.
Here's how I did it:
import UIKit
import Charts
class MyHeader: UIView, ChartViewDelegate {
// My Header is the top view of the ViewController
// It gives summary information for the user
// MARK: - Properties
var firstLabel = LabelView()
var secondLabel = LabelView()
var firstPie = PieChartView()
var secondPie = PieChartView()
var thirdPie = PieChartView()
let nformatter = NumberFormatter()
// MARK: - LifeCycle
override func awakeFromNib() {
super.awakeFromNib()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.white.withAlphaComponent(0.5)
setupStack()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Setup Methods
func setupLabels() {
let lbls = [firstLabel, secondLabel]
for lbl in lbls {
lbl.label.font = UIFont.preferredFont(forTextStyle: .title3)
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
let pies = [firstPie, secondPie, thirdPie]
for pie in pies {
pie.translatesAutoresizingMaskIntoConstraints = false
pie.heightAnchor.constraint(equalToConstant: 100).isActive = true
pie.spanSuperView() // spanSuperView is a UIView extension that I use
pie.delegate = self as ChartViewDelegate
// entry label styling
pie.entryLabelColor = .white
pie.entryLabelFont = .systemFont(ofSize: 12, weight: .light)
pie.animate(xAxisDuration: 1.4, easingOption: .easeOutBack)
}
}
func setupFake() {
let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
let unitsSold = [20.0, 4.0, 6.0, 3.0, 12.0, 16.0]
setChart(dataPoints: months, values: unitsSold)
}
func setChart(dataPoints: [String], values: [Double]) {
var dataEntries: [ChartDataEntry] = []
for i in 0..<dataPoints.count {
let dataEntry = ChartDataEntry(x: values[i], y: Double(i))
dataEntries.append(dataEntry)
}
let set = PieChartDataSet(values: dataEntries, label: "Results")
set.drawIconsEnabled = false
set.sliceSpace = 2
set.colors = ChartColorTemplates.joyful()
+ ChartColorTemplates.colorful()
+ ChartColorTemplates.liberty()
+ ChartColorTemplates.pastel()
+ [UIColor(red: 51/255, green: 181/255, blue: 229/255, alpha: 1)]
let data = PieChartData(dataSet: set)
let pies = [firstPie, secondPie, thirdPie]
for pie in pies {
pie.data = data
}
}
func setupStack(){
setupFake()
setupLabels()
let dashStack = UIStackView(arrangedSubviews: [firstPie, secondPie, thirdPie])
dashStack.axis = .horizontal
dashStack.distribution = .fillEqually
dashStack.alignment = .center
dashStack.spacing = 0
dashStack.translatesAutoresizingMaskIntoConstraints = false
let stackView = UIStackView(arrangedSubviews: [firstLabel, secondLabel, dashStack])
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 0
stackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(stackView)
//autolayout the stack view
stackView.spanSuperView()
}
}

Vote system using double tap

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.

Resources