stop UICollectionView from loading while initializing - ios

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();

Related

How Do I Get Gesture Recognizers to work on base controller when displaying a popup controller? [duplicate]

I have a collection view and when a cell is selected it presents a popover view showing more information about that cell.
I would like to allow the user to click another cell and then have the popover view change to showing that cell's information without having to close the popover. If the user were to click somewhere on the parent view that isn't a cell then the popover should close. But, I would like the user to still be able to scroll the collection view without closing the popover.
How can that be done?
According to Apple :
When a popover is active, interactions with other views are normally disabled until the popover is dismissed. Assigning an array of views to this property allows taps outside of the popover to be handled by the corresponding views.
Then you can use the passthroughViews in the following way :
CollectionViewController
import UIKit
let reuseIdentifier = "Cell"
class CollectionViewController: UICollectionViewController {
var popoverViewController : PopoverViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return 15
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CollectionViewCell
cell.labelInfo.text = "Cell \(indexPath.row)"
return cell
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("tapped")
if let popover = self.popoverViewController {
var cell = self.collectionView!.cellForItemAtIndexPath(indexPath) as! CollectionViewCell
popover.labelPop.text = cell.labelInfo.text
}
else {
self.popoverViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PopoverViewController") as? PopoverViewController
var cell = self.collectionView!.cellForItemAtIndexPath(indexPath) as! CollectionViewCell
var t = self.popoverViewController!.view
self.popoverViewController!.labelPop.text = cell.labelInfo.text
self.popoverViewController!.modalPresentationStyle = .Popover
var popover = self.popoverViewController!.popoverPresentationController
popover?.passthroughViews = [self.view]
popover?.sourceRect = CGRect(x: 250, y: 500, width: 0, height: 0)
self.popoverViewController!.preferredContentSize = CGSizeMake(250, 419)
popover!.sourceView = self.view
self.presentViewController(self.popoverViewController!, animated: true, completion: nil)
}
}
}
The above code is the CollectionViewController to handle the UICollectionViewController and all its delegates.
CollectionViewCell
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var labelInfo: UILabel!
}
The custom cell with just a UILabel inside.
PopoverViewController
class PopoverViewController: UIViewController {
#IBOutlet var labelPop: UILabel!
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.
}
}
And finally the PopoverViewController to show in form of .Popover.
There are some observations in answer I would like to point out :
I set a reference to the class PopoverViewController to keep it through the life cycle and pass it data when it remains open yet.
The line var t = self.popoverViewController!.view it's necessary because if not the #IBOutlet inside the PopoverViewController was not init until it's presented, there could be other ways to do it.
I present the popover in the middle of the screen to handle the tap in several cell and test it the scroll too, you can display it in any position you want.
In the views to allow when the popover is opened , I set the self.view, but in this way you need to dismiss it for you own, because it never is dismissed when you make taps in the view, you can put any view you want instead.
Any trouble you have with the solution I can share it the project on Github.
I hope this help you
What you are looking for is the passthroughViews property of the popover.
However, if you open the popover as a result of tapping a cell, I don't see how scrolling the collectionView will make sense. Don't you open the popover with the arrow pointing to your cell? Scrolling the view will make the presenting cell to move away...
You can use property of UIViewController 'modalInPopover' to enable touches outside the popover boundary. Just write the line given below in your view controller which you are presenting using popover controller.
self.modalInPopover = false;
where self is kind of UIViewController.
I have attached a screenshot for the same.
In swift the line will remain same
self.modalInPopover = false

perform segue from UIView

I have ViewController and there is UIView in it.
This UIView has separate class myView and there are many UI elements - one of them is CollectionView.
What I want is to perform segue when one of collection elements in myView is selected. But when I try to add line
performSegue(withIdentifier: "myIdintifier", sender: self)
to collection's view didSelectItemAt method I get error
Use of unresolved identifier 'performSegue'
And I understand that this is because I do it inside class that extends UIView and not UIViewController.
So how can I perfrom segue in this case? And also how can I prepare for segue?
Here I am going to evaluate it in step by step manner.
Step - 1
Create custom delegate using protocol as below snippet will guide you on your custom UIView. protocol must exist out of your custom view scope.
protocol CellTapped: class {
/// Method
func cellGotTapped(indexOfCell: Int)
}
Don't forgot to create delegate variable of above class as below on your custom view
var delegate: CellTapped!
Go with your collection view didSelect method as below
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if(delegate != nil) {
self.delegate.cellGotTapped(indexOfCell: indexPath.item)
}
}
Step - 2
Let's come to the your view controller. give the CellTapped to your viewcontroller.
class ViewController: UIViewController,CellTapped {
#IBOutlet weak var myView: MyUIView! //Here is your custom view outlet
override func viewDidLoad() {
super.viewDidLoad()
myView.delegate = self //Assign delegate to self
}
// Here you will get the event while you tapped the cell. inside it you can perform your performSegue method.
func cellGotTapped(indexOfCell: Int) {
print("Tapped cell is \(indexOfCell)")
}
}
Hope this will help you.
You can achieve using protocols/delegates.
// At your CustomView
protocol CustomViewProtocol {
// protocol definition goes here
func didClickBtn()
}
var delegate:CustomViewProtocol
#IBAction func buttonClick(sender: UIButton) {
delegate.didClickBtn()
}
//At your target Controller
public class YourViewController: UIViewController,CustomViewProtocol
let customView = CustomView()
customView.delegate = self
func didClickSubmit() {
// Perform your segue here
}
Other than defining protocol, you can also use Notification.
First, extent nonfiction.name:
extension Notification.Name {
static let yourNotificationName = Notification.Name(“yourNotificationName”)
}
Then right where you want to perform segue but can’t in your custom UIView:
NotificationCenter.default.post(name: .yourNotificationName, object: self)
Finally, you can listen to the notification in your viewControllers:
private var observer: NSObjectProtocol?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
observer = NotificationCenter.default.addObserver(forName: .yourNotificationName, object: nil, queue: nil) {notification in
self.performSegue(withIdentifier:”your segue”, sender: notification.object}
Don’t forget to remove it:
override func viewWillDisappear(_ animated: Bool){
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(observer)
}

How to use button click to start animation?

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.

Trigger a function when user taps `Back` in detail view controller

I need to refresh my collectionView when user returns to that VC because what he/she did in the detailVC has affect on the previous VC data. I tried collectionView.reloadData() in both viewDidLoad() and viewDidAppear() of my VC has the collectionView in it. And It came up that when user taps the 'Back' in detailVC both viewDidLoad() and viewDidAppear() do not work. So, I tried to call one of them in detailVC with instantiate the firstVC(which has the collectionView)
then I got an runtime error which said collectionView is nil. Any thoughts? (BTW, the segue between them is ShowPush, and I can not change it because I have to have the transition of this segue in my app.)
Here is the firstVC:
class SkillsController: UIViewController{
#IBOutlet weak var collectionView: UICollectionView!
var TAGS: [TAG] = []
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "TagCell", bundle: nil)
collectionView.register(nib, forCellWithReuseIdentifier: "tagCell")
self.sizingCell = (nib.instantiate(withOwner: nil, options: nil) as NSArray).firstObject as! TagCell?
self.loadMore()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("back to skills")
self.TAGS = TagManager.shared.tagList
collectionView.reloadData()
}
}
TAGS is my data which is stored in Realm database.
Here is the detailVC:
class SeeSelectedController: UICollectionViewController {
var TAGS: [TAG] = []
#IBOutlet weak var layout: FSQCollectionViewAlignedLayout!
override func viewDidLoad() {
super.viewDidLoad()
if currentTab.shared.isSkill {
self.title = "Selected Skills"
//init tags
let list = RealmManager.shared.skills
if let list = list {
for element in list {
TAGS.append(TAG(n: element.value!, iS: true))
}
}
collectionView?.reloadData()
}else{
self.title = "Selected Needs"
//init tags
let list = RealmManager.shared.needs
if let list = list {
for element in list {
TAGS.append(TAG(n: element.value!, iS: true))
}
}
collectionView?.reloadData()
}
let nib = UINib(nibName: "TagCell", bundle: nil)
collectionView?.register(nib, forCellWithReuseIdentifier: "tagCell")
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = TAGS[indexPath.row].name!
let currentState = TAGS[indexPath.row].isSelected!
TAGS[indexPath.row].isSelected = currentState ? false:true
if currentState {
print("deselect")
//remove from realm
RealmManager.shared.deleteItemFromList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}else{
print("select")
//add to realm
RealmManager.shared.addItemToList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}
if currentTab.shared.isSkill {
let VC: SkillsController = storyboard?.instantiateViewController(withIdentifier: "SkillsController") as! SkillsController
VC.viewDidAppear(true)
}
collectionView.reloadData()
//addd
}
}
So how it is working? in the SkillsVC user can select some tags from a pool, in the detailVC which is SeeSelecteVC he/she can drop selected tags. It is constantly changing in the Realm as you can see. The problem when user has dropped some tags in detailVC and press the Back button, the dropped tags are still looking as selected in SkillsVC. However when if user goes another VC and comes back to SkillsVC (by this way the viewDidLoad() is gonna work) the dropped tags are seems to be unselected. That's all.
If what you are looking for is just to reload on back button
What you can do is create your own custom UIBarButtonItem that will make you navigate backwards from your "detail view controller". What you should do next after adding your own back button is add an IBAction for UIBarBUttonItem and pop your "detail view controller".
Right before you do this, you should create a delegate that will be executed before the popping happens that will reload your UICollectionView.
The following is not the best way to achieve what you want:
In your didSelectItem for your second view controller, you are creating a new view controller here and you shouldn't force call viewDidAppear. Since you are creating a new UIViewController, you are not referencing the previous UIViewController that you came from and soo your UICollectionView is nil.
if currentTab.shared.isSkill {
//remove the below lines and call the delegate here
let VC: SkillsController = storyboard?.instantiateViewController(withIdentifier: "SkillsController") as! SkillsController
VC.viewDidAppear(true)
}
collectionView.reloadData()
What you should be doing is:
You should use delegates to send callbacks to previous view controllers or perform actions.
To create a delegate-
Using the first approach (using your own back button)-
protocol delegateVC{
func reloadCollectionView()
}
class SeeSelectedController: UICollectionViewController{
//add this inside this class
var delegate : delegateVC?
...
//implement your IBAction for back button and inside it-
... {
self.delegate.reloadCollectionView()
}
}
OR the second approach i pointed out (Just change your didSelectItem and it will reload the collectionView, no need to fret about back button at all and save the hassle, i strongly recommend this approach)
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = TAGS[indexPath.row].name!
let currentState = TAGS[indexPath.row].isSelected!
TAGS[indexPath.row].isSelected = currentState ? false:true
if currentState {
print("deselect")
//remove from realm
RealmManager.shared.deleteItemFromList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}else{
print("select")
//add to realm
RealmManager.shared.addItemToList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}
if currentTab.shared.isSkill {
self.delegate.reloadCollectionView()
}
}
}
And in your first view controller-
func reloadCollectionView(){
collectionView.reloadData()
}
Note: In your prepareForSegue remember to set the delegate of your detail view controller to be your first view controller

Swift – Table view data not reloading after dismissing view controller

I have a view in my app called JournalViewController that I'm presenting over my PastSessionsViewController. PastSessions has a table view that the user can tap to edit and bring up the journal.
When the user edits an entry and saves it (saving to CoreData), dismissing JournalViewController I'd like for the table view in PastSessions to reflect those changes and show the updated table cell.
I'm calling tableView.reloadData() in PastSessionsViewController viewDidLoad() but that doesn't seem to be working. I've also added a delegate for JournalViewController to interact with PastSessionsViewController ahead of dismissViewController
Here's some code to look at:
In PastSessionsViewController:
class PastSessionsViewController: UIViewController, UITableViewDelegate, JournalVCDelegate {
weak var tableView: UITableView?
weak var backButton: UIButton?
let pastSessionsDataSource: PastSessionsDataSource
init() {
pastSessionsDataSource = PastSessionsDataSource()
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView()
tableView.backgroundColor = nil
tableView.delegate = self
tableView.dataSource = pastSessionsDataSource
tableView.registerClass(EntryCell.self, forCellReuseIdentifier: "cell")
view.addSubview(tableView)
self.tableView = tableView
}
override func viewDidAppear(animated: Bool) {
tableView?.reloadData()
}
func didFinishJournalVC(controller: JournalViewController) {
var newDataSource = PastSessionsDataSource()
tableView?.dataSource = newDataSource
// tried this ^, but it's causing the app to crash
// tableView?.reloadData() <- this isn't doing the trick either
dismissViewControllerAnimated(true, completion: nil)
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let editJournalVC = JournalViewController(label: "Edit your thoughts")
editJournalVC.delegate = self
presentViewController(editJournalVC, animated: true, completion: nil)
}
}
In JournalViewController:
protocol JournalVCDelegate {
func didFinishJournalVC(controller: JournalViewController)
}
class JournalViewController: UIViewController, UITextViewDelegate {
var delegate: JournalVCDelegate! = nil
func doneJournalEntry(sender: UIButton) {
journalEntryTextArea?.resignFirstResponder()
... do some core data saving ...
delegate.didFinishJournalVC(self)
}
}
In PastSessionsDataSource:
import UIKit
import CoreData
class PastSessionsDataSource: NSObject {
var arrayOfEntries = [Entry]()
var coreDataReturn: [Meditation]?
func prepareEntries() {
// gets stuff from coredata and formats it appropriately
}
override init() {
super.init()
prepareEntries()
}
}
extension PastSessionsDataSource: UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayOfEntries.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! EntryCell
... set up the labels in the cell ...
return cell
}
}
Thanks for looking!
viewDidLoad is called when the view controller load its view at the first time, so basically it will only be called once during the view controller's whole life cycle.
One quick solution is to put tableView.reloadData() in PastSessionsViewController viewWillAppear() or viewDidAppear().
However I do not like this quick solution as every time you dismiss JournalViewController, the table view will be reloaded, even the user has not changed anything on JournalViewController (for example, cancel the edit). So I suggest to use delegate approach between PastSessionsViewController and JournalViewController, when the user actually edit the data on JournalViewController then inform PastSessionsViewController to refresh the table.
You are currently prepare entries only on init of PastSessionsDataSource, but not after you did CoreData changes. So each time when you reloadData for tableView you work with the same data set loaded initially. As a quick hack you can try to updated viewDidAppear in a following way:
override func viewDidAppear(animated: Bool) {
if let tableView = tableView {
let dataSource = tableView.dataSource! as PastSessionsDataSource
dataSource.prepareEntries()
tableView.reloadData()
}
}
Your tableView property is probably nil in viewDidAppear, based on your listed code. The reason is that in viewDidLoad you construct a UITableView as tableView, and that is a local variable. You need to assign that variable to the property:
self.tableView = tableView

Resources