How to use button click to start animation? - ios

I can't seem to figure out how to start an animation after my "Play" button has been clicked from my UICollectionViewCell. Here is some of my code below, any help please?
I have other code that pertains to my collectionView setup but not sure if you need to see it or not.
It seems, I'm only able to run the animation once the viewAppears but how do you initiate an animation well after the viewAppears?
Here is my UICollectionViewCell code:
import UIKit
class CreateCollectionViewCell: UICollectionViewCell {
var animateDelegate: AnimateScenesDelegate!
#IBOutlet weak var scenes: UIImageView!
#IBAction func scenePlay(sender: UIButton) {
animateDelegate.animateScenes()
let playButtonFromCreateCollection = scenes.image!
print("It was this button \(playButtonFromCreateCollection)")
}
}
HERE is some of my UIViewController Code:
class CreateViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, AnimateScenesDelegate {
#IBOutlet weak var StoryViewFinal: UIImageView!
var scene01_68: [UIImage] = []
func animateScenes () {
print("Play button was pressed")
StoryViewFinal.animationImages = scene01_68
StoryViewFinal.animationDuration = 15.0
StoryViewFinal.animationRepeatCount = 1
StoryViewFinal.startAnimating()
}
func loadScenes () {
for i in 1...158 {
scene01_68.append(UIImage(named: "Scene01_\(i)")!)
print(scene01_68.count)
}
}
override func viewDidAppear(animated: Bool) {
animateScenes()
super.viewDidLoad()
loadScenes ()
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
\\ OTHER CODE...
cell.animateDelegate = self
return cellA
}

It seems to be the case that you want: When a button is tapped in the cell, the view controller should perform some animation? This issue comes from needing better coordination between the cell and the view controller. Cells are just views and don't have the knowledge to do anything outside themselves.
When the view controller formats the cell in cellForItemAtIndexPath, you need to give it a "perform animation delegate" performAnimationDelegate. This is a reference back to the view controller.
protocol AnimateScenesDelegate {
func animateScenes()
}
class CreateCollectionViewCell: UICollectionViewCell {
weak var animateDelegate : AnimateScenesDelegate
#IBAction func scenePlay(sender: UIButton) {
animateDelegate?.animateScenes()
}
}
class CreateViewController: UIViewController, ... AnimateScenesDelegate {
func animateScenes() {
//Animate here ...
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//...
cell.animateDelegate = self
}
}
Note the weak var on the cell delegate, because you don't want the cell to keep the view controller alive.
This is not the only way to do this but it's established and simple. Remember that the delegate (view controller) doesn't have any information about what is calling it, so you would have to add a parameter or check if you wanted to know for example which cell is being tapped. Hope this helps.

Related

Swift 4.2: Unable to add custom behaviour to a custom UITableViewCell using Protocol oriented delegate concepts

I've a custom UITableViewCell, in that I've two UILabels & one UIButton. I'm able to load data...and display it as per requirement.
Problem Statement-1: Now problem exist in my UIButton, which is in my UICustomTableViewCell. Due to this I'm unable to handle click event on that UIButton.
Problem Statement-2: On button Click I have to identify the index of that Button click and pass data to next ViewController using segue.
Now have a look on...what did I've tried for this...
Yes, first-of-all I have thought that Binding IBOutlet action in my CustomCell will resolve my problem...but actually it doesn't solved my problem.
After that I've accessed button using .tag and initialised index path.row to it.
But it won't helped me.
So now I'm using Protocol oriented concept using delegate to handle click event on my UIButton which is available in CustomCell.
What did I tried:
SwiftyTableViewCellDelegate:
protocol SwiftyTableViewCellDelegate : class {
func btnAuditTrailDidTapButton(_ sender: LeadCustomTableViewCell)
}
CustomTableViewCell with delegate:
class LeadCustomTableViewCell: UITableViewCell {
#IBOutlet weak var lblMeetingPersonName: UILabel!
#IBOutlet weak var lblPolicyNo: UILabel!
#IBOutlet weak var btnLeadAuditTrail: UIButton!
weak var delegate: SwiftyTableViewCellDelegate?
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func btnAuditTrailTapped(_ sender: UIButton) {
delegate?.btnAuditTrailDidTapButton(self)
}
}
ViewController implementing delegate:
class LeadViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, SwiftyTableViewCellDelegate {
//IBOutlet Connections - for UITableView
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//setting dataSource & delegates of UITableView with this ViewController
self.tableView.dataSource = self
self.tableView.delegate = self
//Reloading tableview with updated data
self.tableView.reloadData()
//Removing extra empty cells from UITableView
self.tableView.tableFooterView = UIView()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:LeadCustomTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! LeadCustomTableViewCell
//Assigning respective array to its associated label
cell.lblMeetingPersonName.text = (meetingPersonNameArray[indexPath.section] )
cell.lblPolicyNo.text = (String(policyNoArray[indexPath.section]))
cell.btnLeadAuditTrail.tag = indexPath.section
cell.delegate = self
return cell
}
//This is delegate function to handle buttonClick event
func btnAuditTrailDidTapButton(_ sender: LeadCustomTableViewCell) {
guard let tappedIndexPath = tableView.indexPath(for: sender) else { return }
print("AuditTrailButtonClick", sender, tappedIndexPath)
}
Don't know why this is not working.
Link the touch up inside event in cellForRow by adding the following code:
cell.btnLeadAuditTrail.addTarget(self, action:#selector(btnAuditTrailDidTapButton(_:)), for: .touchUpInside)

Perform a segue selection from a uicollectionview that is embedded in a tableview cell?

Currently we have a uicollectionview that is embedded in a tableview cell. When the collection view cell is selected it's suppose to initiate a push segue to another view controller. The problem is there is no option to perform the segue on the cell. Is there a way around it? Here is the cell:
class CastCell : UITableViewCell {
var castPhotosArray: [CastData] = []
let extraImageReuseIdentifier = "castCollectCell"
let detailToPeopleSegueIdentifier = "detailToPeopleSegue"
var castID: NSNumber?
#IBOutlet weak var castCollectiontView: UICollectionView!
override func awakeFromNib() {
castCollectiontView.delegate = self
castCollectiontView.dataSource = self
}
}
extension CastCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return castPhotosArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = castCollectiontView.dequeueReusableCell(withReuseIdentifier: extraImageReuseIdentifier, for: indexPath) as! CastCollectionViewCell
cell.actorName.text = castPhotosArray[indexPath.row].name
return cell
}
}
extension CastCell: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.castID = castPhotosArray[indexPath.row].id
performSegue(withIdentifier: detailToPeopleSegueIdentifier, sender: self) //Use of unresolved identifier 'performSegue' error
}
}
extension CastCell {
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let peopleVC = segue.destination as! PeopleDetailViewController
peopleVC.id = self.castID
}
}
The problem is there is no option to perform the segue on the cell
There is no such thing as a "segue on a cell". A segue is from one view controller to another. performSegue is a UIViewController method. So you cannot say performSegue from within your CastCell class, because that means self.performSegue, and self is a UITableViewCell — which has no performSegue method.
The solution, therefore, is to get yourself a reference to the view controller that controls this scene, and call performSegue on that.
In a situation like yours, the way I like to get this reference is by walking up the responder chain. Thus:
var r : UIResponder! = self
repeat { r = r.next } while !(r is UIViewController)
(r as! UIViewController).performSegue(
withIdentifier: detailToPeopleSegueIdentifier, sender: self)
1: A clean method is to create a delegate protocol inside your UITableViewCell class and set the UIViewController as the responder.
2: Once UICollectionViewCell gets tapped, handle the taps inside the UITableViewCell and forward the tap to your UIViewController responder through delegatation.
3: Inside your UIViewController, you can act on the tap and perform/push/present whatever you want from there.
You want your UIViewController to know what is happening, and not call push/presents from "invisible" subclasses that should not handle those methods.
This way, you can also use the delegate protocol for future and other methods that you need to forward to your UIViewController if needed, clean and easy.

is there a way of refreshing the whole UITableView through a button that is in one of the cells?

I have a dynamically generated UITableView with many dynamic UITableViewCells and one static UITableViewCell.
The static one has a button and I want to refresh the whole table view when user presses it.
My code attached to the cell is simple:
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
#IBAction func sendCommentButtonAction(sender: AnyObject) {
//from here I want to refresh the table
}
}
How can I refresh the parent table from that button? In the class MyStaticCell I don't have any instance of the table, so that's my problem for now :|
The cleanest way to do this is through delegation. This ensures that the cell class doesn't need to know what should happen when the button is pressed; that logic can remain in your view controller where it belongs.
protocol CommentButtonProtocol {
func commentButtonTapped(sender: MyStaticCell)
}
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
var delegate: CommentButtonProtocol?
#IBAction func sendCommentButtonAction(sender: AnyObject) {
self.delegate?.commentButtonTapped(self)
}
}
Then in your view controller you can set it as the delegate in cellForRowAtIndexPath and comply with the protocol in order to handle the event:
class ViewController: UIViewController, CommentButtonProtocol {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("staticCell", forIndexPath: indexPath) as! MyStaticCell
cell.delegate = self
return cell
}
func commentButtonTapped(sender: MyStaticCell) {
// Do whatever you need to do when the button is tapped
}
}
You could access the tableView using superview.
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
#IBAction func sendCommentButtonAction(sender: AnyObject) {
(superview as? UITableView)?.reloadData()
}
}
This isn't as stable as it could be so maybe consider this extension:
extension UIResponder {
func nextResponder<T: UIResponder>(ofType type: T.Type) -> T? {
switch nextResponder() {
case let responder as T:
return responder
case let .Some(responder):
return responder.nextResponder(ofType: type)
default:
return nil
}
}
}
It allows you to find the next parent of a particular type, in the cells case, a UITableView.
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
#IBAction func sendCommentButtonAction(sender: AnyObject) {
nextResponder(ofType: UITableView.self)?.reloadData()
}
}

Perform Segue from Collection View Cell

import UIKit
class ActionCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var actionGIF: UIImageView!
#IBAction func actionPressed(sender: AnyObject) {
print(myLabel.text)
Global.actionButtonIndex = myLabel.text!.toInt()! - 1
print(actionGIF.image)
ActionViewController.performSegueWithIdentifier("showActionPreview", sender: nil)
}
}
I am trying to perform a Segue after User Clicking on One of the Cell in my Collection View. Can't seem to do that using performSegueWithIdentifier. App Screenshot
Here's an elegant solution that only requires a few lines of code:
Create a custom UICollectionViewCell subclass
Using storyboards, define an IBAction for the "Touch Up Inside" event of your button
Define a closure
Call the closure from the IBAction
Swift 4+ code
class MyCustomCell: UICollectionViewCell {
static let reuseIdentifier = "MyCustomCell"
#IBAction func onAddToCartPressed(_ sender: Any) {
addButtonTapAction?()
}
var addButtonTapAction : (()->())?
}
Next, implement the logic you want to execute inside the closure in your
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCustomCell.reuseIdentifier, for: indexPath) as? MyCustomCell else {
fatalError("Unexpected Index Path")
}
// Configure the cell
// ...
cell.addButtonTapAction = {
// implement your logic here, e.g. call preformSegue()
self.performSegue(withIdentifier: "your segue", sender: self)
}
return cell
}
You can use this approach also with table view controllers.
Instance method performSegue is not available from a UICollectionViewCell:
Since an UICollectionViewCell is not an UIViewController, you can not use performSegue(withIdentifier:sender:) from it. You may prefer use delegates to notify your parent view controller and then, performSegue from there.
Take a look at the details of this answer. The question is slightly different but the solution lies in the same pattern.
Have you set the segue identifier by exactly named "showActionPreview". Moreover, ensure that your segue linked from your parent view controller to your destination view controller in storyboard. Hope this would be help.

stop UICollectionView from loading while initializing

I have a UICollectionView on a separate view let say "dashboardView" for the partial code is giving below,
class DashBoardView: UIView
{
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
collectionView.dataSource=self
collectionView.delegate=self
let nibLineChart = UINib(nibName: "GraphCell", bundle: nil)
collectionView.registerNib(nibLineChart, forCellWithReuseIdentifier:lineChartIdentifier )
}
}
Now this view is embeded in a UIViewController and the sample code is giving below,
class ViewController : UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
graphicalDashBoardView = DashBoardView.instanceFromNib() as! DashBoardView
}
}
now what it does as soon the viewdidload call finishes it tries to render the UIcollectionView in the inner view and call the cellForItemAtIndexPath. I just dont want the uicollectionview to load when it finishes viewdidload call. I rather want to load this collectionview on a button click. I know i can reload it by using the collectionview.reloadData() but how i should stop it for the first time loading.
Any help would be appreciable.
You may try this solution.
Don't fill the items when viewdidload delegate method is called.
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
On button tap fill the items and collection view.reloadData().
This should work.
Remove the code from your viewdidload so that it doesn't load at the beginning. Add a button and connect it with a button click event. Inside the click event add your code to load the uicollectionview.
Try this code:
class ViewController : UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
}
}
#IBAction func buttonPressed(sender: AnyObject) {
graphicalDashBoardView = DashBoardView.instanceFromNib() as! DashBoardView
}
not saying it's elegant, but you could make a
var renderCollection: bool = false
and in your numberOfSectionsInCollectionView and numberOfItemsInSection functions add
if !renderCollection return 0
until the moment you want the collection to load when you would do
renderCollection = true ; collectionView.reloadData();

Resources