I have a scenario in which I require to display some data in UITableView when user clicks on a particular UIButton and display the same data in UICollectionView when clicked on another UIbutton . But in the same view controller .
First when table view is selected we are going for data in table view -
it would show data in table view format
Second when collection view button is clicked we are going for displaying data in collection view.
I think we can achieve this by the following ways -
1)Creating 2 views in scene dock one having table view and the other having collection view and using them in our code.
2)Creating 2 .xib files of UIView class having TableView & CollectionView and loading them on selection of particular button.
So how could I achieve this functionality? Please suggest some ways which could be quite reliable in such cases and if possible share any source codes or links or give detailed explanation which could help iOS novices like me.
You could use UIPageViewController. Just remember to disable scrolling of pageviewcontroller.
It seems like to make custom view container.
Google with keywords iOS custom container, there are many resources.
Custom container view controller transitions
Container View controller quick start
Apple document, about custom container
You can also add both the UItableView and UICollectionView in the same view and make one of them hidden in each state. So by default the first button is selected where the collectionView will be hidden. When user taps on the second button you can hide the table view and unhide the collection view. You can also add animation for transitioning two state to make it look good.
If we simply think about this problem then we can use only one collectionView with two different kind of Layouts and cellViews of different sizes. But I am not sure, how much will be the complexity with Aujtolayout (if you using).
First create two class file with UITableView and UICollectionView baseClass for more detail see below screenshot.
And add datasource and delegate method for both class in implementation file.
Now add following code in .h(interface) file where you want to show table/collection view.
#import "collectionData.h"
#import "tableData.h"
#interface ViewController : UIViewController {
collectionData *collectionObj;
tableData *tableObj;
IBOutlet UIView *viewData;
}
#end
And in .m(implementation) file:
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *arrCollection = [[NSBundle mainBundle] loadNibNamed:#"collectionData" owner:self options:nil];
collectionObj = (collectionData *)[arrCollection objectAtIndex:0];
NSArray *arrTable = [[NSBundle mainBundle] loadNibNamed:#"tableData" owner:self options:nil];
tableObj = (tableData *)[arrTable objectAtIndex:0];
[self displayView:tableObj];
}
- (IBAction)btnTableClick:(id)sender {
[self displayView:tableObj];
}
- (IBAction)btnCollectionClick:(id)sender {
[self displayView:collectionObj];
}
- (void)displayView: (UIView *)viewObj {
for(UIView *view in viewData.subviews) {
[view removeFromSuperview];
}
[viewData addSubview:viewObj];
}
Note that its not everything yet that you want it's only basic idea.
Your initial view controller can house the buttons and a "Container View Controller" that will segue between a UITableViewController and a UICollectionViewController based on the button that was pressed. When a button is tapped, the initial view controller will pass along the product data and make itself the child view controller's delegate.
When a cell is selected in either of your child view controllers, they can call methods on the delegate (your initial view controller) and it can act accordingly.
To pass data into whatever child view controller you want to display first (e.g. the UITableViewController), perform a segue when the initial view controller loads.
I would personally suggest any one facing such type of situation to go for Scene Dock which many don't care to use. It requires quite less coding and is very easy. I think it can be considered as an alternative for .xib .
I followed some details about scene dock in the link - http://www.raywenderlich.com/115697/ios-9-storyboards-tutorial-whats-new-in-storyboards
Add a table view and a collection view in the scene dock of your view controller and go for the coding. Here is an example of hoe it can be done.
Main story board -
Here goes the coding with all explannations -
import UIKit
class CategoriesViewController: UIViewController,UITableViewDataSource,UITableViewDelegate,UICollectionViewDataSource,UICollectionViewDelegate
{
var mainarray : NSMutableArray!
var cararray : NSMutableArray!
var bikearray : NSMutableArray!
#IBOutlet var TableVieww: UITableView!//table view outlet in scene dock
#IBOutlet var collctnvieww: UICollectionView!//scene dock collection view outlet
#IBOutlet weak var rqdview: UIView!// view on which you would be adding the collection/table view
override func viewDidLoad()
{
super.viewDidLoad()
cararray = ["audii","duster"]
bikearray = ["honda","bajaj"]
TableVieww.dataSource = self
TableVieww.delegate = self
collctnvieww.delegate = self
collctnvieww.dataSource = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func Car(sender: AnyObject)//outlet of car button
{
mainarray = cararray as NSMutableArray
self.TableVieww.reloadData()
self.collctnvieww.reloadData()
}
#IBAction func bike(sender: AnyObject)//outlet of bike button
{
mainarray = bikearray as NSMutableArray
self.TableVieww.reloadData()
self.collctnvieww.reloadData()
}
#IBAction func showcllctnview(sender: AnyObject)//when you want to show collection view for cars/bikes
{
print("collection view called")
collctnvieww.frame = CGRectMake(0, 0, rqdview.frame.width, rqdview.frame.height)//you change the frame size of your views as per your requirement.
self.rqdview.addSubview(collctnvieww)
}
#IBAction func showtableview(sender: AnyObject)//when you want to show table view for cars/bikes
{
print("table view called")
TableVieww.frame = CGRectMake(0, 0, rqdview.frame.width, rqdview.frame.height)
self.rqdview.addSubview(TableVieww)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return mainarray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = TableVieww.dequeueReusableCellWithIdentifier("cellreuse", forIndexPath: indexPath)
cell.textLabel!.text = mainarray[indexPath.row] as? String
return cell
}
//coding for collection view
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return mainarray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collctnvieww.dequeueReusableCellWithReuseIdentifier("collectioncell", forIndexPath: indexPath) as! CategoriesCollectionViewCell
cell.nmlbl.text = mainarray[indexPath.row] as? String
return cell
}
}
But still I think the code can be shortened if I can use only one data source for table view and collection view. If any one can suggest some tips, it might be helpful.
Related
I'm trying to create a ViewController which would have swipe-able (android like tabs) pages. These pages themselves will have scrollviews(vertical) inside them and multiple views which would be added dynamically depending on type of response (different network calls for each page). I can't use a PageViewController as I want the pages to take up only half the screen.
Issues with CollectionView -
If the cells would get reused (or removed from memory), how would I maintain the state of the cell's UI and store that data (especially difficult for views as each page might have different type of view in them)
Issues with ScrollView -
I'm worried if there would be memory issues if all page view controllers would be in memory with each view in it
PS - data in each page would be 4-10 stackviews each containing 2-10 images/labels OR just one collectionview
PSS - Total number of tabs wouldn't exceed 10, minimum would be 1
I'd implemented it with collectionView cause it should be really more resource effective. But then we need to cache states of view controllers. Here is the example
Let's say you have controller A which contains collectionView with cell with your child controllers. Then in cell for row
....
var childrenVC: [Int: UIViewController] = [:]
....
// cell for row
let cell: ChildControllerCell = collectionView.dequeueReusableCell(for: indexPath)
if let childController = childrenVC[indexPath.row] {
cell.contentView.addSubview(childController.view)
childController.view.frame = cell.contentView.frame
} else {
let childViewController = ChildViewController()
addChildViewController(childViewController)
childViewController.didMove(toParentViewController: self)
cell.contentView.addSubview(childController.view)
childController.view.frame = cell.contentView.frame
childrenVC[indexPath.row] = childViewController
cell.childVC = childViewController
}
return cell
....
class ChildControllerCell: UICollectionViewCell {
var childVC: UIViewController?
override func prepareForReuse() {
super.prepareForReuse()
if !contentView.subviews.isEmpty {
childVC?.willMove(toParentViewController: nil)
childVC?.view.removeFromSuperview()
childVC?.removeFromParentViewController()
}
}
}
I am making a table view app which retrieves the data from the Firebase. when making the user interface in the storyboard, I am using dummy image and label to visualize my app.
but when i run the app which consists of dynamic table view, those dummy images and label also shows up before immediately replaced by the actual data that i download from the Firebase storage.
can I set those images and labels to not show up when i run the app but still available in the storyboard?
thanks in advance :)
If they're just dummies, you can get rid of them when your view loads, before it appears onscreen:
override func viewDidLoad() -> Void{
super.viewDidLoad()
dummy.removeFromSuperview()
}
Whenever you want to hide/show an UIView:
myView.isHidden = true // hide
myView.isHidden = false // show
I assume what you need is to hide the views in viewWillAppear and then show them when necessary.
In your custom cell class, define something to hide the unwanted views:
func hideDummyViews() {
// do some stuff to hide what you don't want, e.g.
myEnclosingStackView.isHidden = true
}
In your table view controller, in the cellForRowAt indexPath func:
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
// Configure the cell ..
if yourDataSource[indexPath.row].isCompletelyLoaded {
// do your fancy dynamic cell layout
} else {
// show the basic version (minus any dummy views)
cell.hideDummyViews()
}
return cell
You can choose your preferred method for hiding the items (isHidden for each view, removing, adjusting constraints). I prefer to embed any disappearing views in a stack view and then use isHidden = true on the enclosing stack. This keeps things organized in your storyboard/XIB file and neatly recalculates constraints for the hidden stacks.
It seems that you want to show some empty (or incomplete) cells until database content arrives and then you will reload each cell as you process new entries in the datasource. This answer will initially give you a set of cells appearing as per your storyboard/XIB, minus the hidden dummy elements. Then as items in your datasource are loaded fully, you can reload the cells.
By the way, it seems like a lot of work to carefully layout these dummy views for "visualization" and then never show them in the app. Why not have some user-friendly place holders or progress indicators showing and then animate in the real/dynamic views as the data arrives?
I assume you download the Image from FireBase and does't want the dummy Image to appear
(Why dont you declare an empty ImageView and an empty Label!). Try setting an Image array in the viewController. Or you can use a struct array if you want a Image and label text together.
var arrImage=[UIImage]()
var arrLblTxt=[String]()
In view did load append your Demo Image's if required.
arrImage.append(UIImage("Demo"))
arrLblTxt.append("Demo")
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tbl_AssignEmployees.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as YourCell
if arrImage[IndexPath.row] != UIImage("Demo")
{
cell.ImageView.Image = arrImage[indexPath.row]
cell.textLabel!.text = arrLblTxt[indexPath.row]
}
else
{
cell.ImageView.Image = nil
cell.textLabel!.text = arrLblTxt[indexPath.row]
}
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsetsZero
cell.layoutMargins = UIEdgeInsetsZero
return cell
}
Then After you download your Image From FireBase,
Reset your Image array with the new Images downloaded from fireBase
Then reload your tableView. tableView.reloadData
I’m new to swift dev and stack overflow, I’ll really appreciate if someone could shed some light over a search bar sizing issue I’m having. I’m not being able to correctly size a search bar in the screen. I created a simple version of my project to simplify the reproduction of the odd behavior. It’s a single ViewController with a tableView object to display results of a searchBar search. I don’t want the search bar to roll up along with the table cells, so I created a UIView as placeholder for the searchBar just above the tableView in the Main.storyboard and added necessary constraints to them, so they could be resized correctly with different screen sizes and orientations — indeed, auto-layout seems to be working fine.
Please see the image links as I still can't post images here. Main.storyboard image
I also don’t want to use a UISearchBar object in the storyboard because I want to have control over it in my code later. So I added the SearchController searchBar programmatically as a UIview subview with (apparently all) the needed constraints to be resized along with the UIview. Also, as you can see, tableView and the UIView widths are a little smaller than the screen width, it’s one difference to other posts I found, they were all fully stretched to screen edges — I don’t know if this is important or not. I'm using swift 4 on xcode 9.0.1 (9A1004).
The issue:
As you can see the searchBar right edge passes over the UIView frame right edge. When it's tapped the cancel button passes over the screen limit as well.
searchBar image 1
It's even more weird, when orientation is changed to landscape, searchBar width initially doesn’t stretch with the UIView. But when searchBar is tapped its width returns to pass over the screen limit. And when cancel is tapped searchBar width continues greater than the screen width. This behavior occurs no matter screen size and orientation I choose.
What I tried so far:
This project contains all the recommended properties and constraints I could find, with the exception of the property setTranslatesAutoresizingMaskIntoConstraints which I didn't manage to use. On swift 4, I only found a similar one translatesAutoresizingMaskIntoConstraints, but I couldn’t make it work, it returns “Cannot call value of non-function type 'Bool’”.
// searchController.searchBar.setTranslatesAutoresizingMaskIntoConstraints(true)
searchController.searchBar.translatesAutoresizingMaskIntoConstraints(true) (!) Cannot call value of non-function type 'Bool'
The post Display UISearchController's searchbar programmatically is a very good one, it’s the most similar to mine (except that it extends to the left, and my case it does do the right), but it didn’t work as well. I also realized that #kcstricks sets the searchBar width to self.view.frame.size.width instead of the UIview frame width.
// self.searchController.searchBar.frame.size.width = self.view.frame.size.width
self.searchController.searchBar.frame.size.width = searchBarPlaceholderView.frame.size.width
By changing it, solved the initial searchBar behavior. Now it's initially shown correctly:
searchBar image 2
But once it’s tapped the odd behavior returns and never gets back to normal again.
searchBar image 3
searchBar image 4
I also tried moving the main code to viewDidAppear method, but no results.
Where am I failing at?
Other researched stack overflow posts:
Fixed UISearchBar using UISearchController - Not using header view of UITableView
UISearchController's searchBar doesn't fill full width
UISearchBar width doesn't change when its embedded inside a UINavigationBar
Thank you very much in advance.
Complete code:
// ViewController.swift
// SearchBarTests
// Copyright © 2017 equilibrio. All rights reserved.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchResultsUpdating {
#IBOutlet var myTableView: UITableView!
#IBOutlet var searchBarPlaceholderView: UIView!
var searchController: UISearchController!
var selectedBeers = [Beer]()
struct Beer {
var type = String()
var examples = String()
}
var beers = [Beer(type: "American Lager", examples: "Budweiser, Coors, Pabst Blue Ribbon"),
Beer(type: "German Helles", examples: "Victory Helles Lager, Stoudt's Gold Lager"),
Beer(type: "German Pilsner", examples: "Tröegs Sunshine Pils, Bavaria, Sierra Nevada's Nooner Pilsner"),
Beer(type: "Belgian Gueuze", examples: "")]
// Delegates and Datasources
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.selectedBeers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")
cell.textLabel?.text = self.selectedBeers[indexPath.row].type
cell.detailTextLabel?.text = self.selectedBeers[indexPath.row].examples
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Row \(indexPath.row) selected")
}
func updateSearchResults(for searchController: UISearchController) {
if searchController.searchBar.text! == "" {
selectedBeers = beers
} else {
selectedBeers = beers.filter { $0.type.lowercased().contains(searchController.searchBar.text!.lowercased()) }
}
self.myTableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
selectedBeers = beers
self.searchController = UISearchController(searchResultsController: nil)
self.searchController.searchResultsUpdater = self
self.searchController.dimsBackgroundDuringPresentation = false
self.searchController.searchBar.delegate = self
definesPresentationContext = true
self.searchBarPlaceholderView.addSubview(self.searchController.searchBar)
self.searchController.searchBar.searchBarStyle = UISearchBarStyle.minimal
// self.searchController.searchBar.frame.size.width = self.view.frame.size.width
self.searchController.searchBar.placeholder = "Type desired beer style..."
self.searchController.searchBar.frame.size.width = searchBarPlaceholderView.frame.size.width
self.searchController.searchBar.sizeToFit()
// searchController.searchBar.setTranslatesAutoresizingMaskIntoConstraints(false)
// searchController.searchBar.translatesAutoresizingMaskIntoConstraints(true)
self.myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.myTableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I had the same issue. I added frame adjusting code to all over the place. For example when i choose an item from tableview suggestion list i call a function. Inside that function i wrote code below, so searchbar came back to width i wanted after animating. I hope somebody can give more professional solution. It doesnt look nice in my code but works.
func(youCallbyTapping) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
var searchBarFrame = self.fromSearchController.searchBar.frame
searchBarFrame.size.width = self.myFrom.frame.size.width - 15
self.fromSearchController.searchBar.frame = searchBarFrame
self.toSearchController.searchBar.frame = searchBarFrame
}
}
I'm interested in creating a view that contains multiple features that users can scroll down and see i.e. pictures, description, comments, carousels etc. I am aware that the UICollectionView is able to provide this type of layout. I initially thought UITableViews would be the best approach.
I have looked at several tutorials and GitHub repos but majority of them just use a UICollectionView in a standard grid layout. I've also looked at IGListKit used by Instagram and some the tutorials linked to it.
I'm aiming to get something like the KitchenStories app:
I was wondering if someone could advice me in terms of the direction and approach best for this.
Don't try to do too much with any single view, even a UICollectionView.
The screen you've shown has a UITabBarController manage its top-level arrangement. The currently selected tab (“Home”) has a UINavigationController managing its content.
On the top of the navigation stack is, probably, a collection view or a table view. Either could be used here because the elements are visually laid out as screen-width rows in a stack. A table view is simpler because then you don't have to worry about setting up the layout.
The table view has several visible rows, each different:
The title/image row (“Easy seafood paella”)
The ratings row
The export row (hearts / save / share)
The comments row
The creator row (I assume, since it looks like it's probably a headshot and a name)
And there are probably even more unique rows out of view.
In your storyboard, you can design each of these rows as a prototype row in the table view controller's scene. Or you can design the table view with static content rows, which is easier if you won't need to change the order of the rows or duplicate any rows at run time.
“But Rob,” you say, “I can't fit all those rows into the table view in my storyboard!” Make the storyboard scene taller. UIKit will resize it at run time to fit the device screen.
In each row, drag in and arrange whatever subviews you need for that row's data. For example, the title/image row needs a UIImageView and a UILabel. The ratings row needs a label, probably a custom view to display and edit the stars, and perhaps a stack view for layout.
For each row, you'll need a separate subclass of UITableViewCell with outlets to that row's views. To pass the data to each cell for display, make each cell conform to a protocol:
protocol RecipeUsing {
var recipe: Recipe? { get set }
}
Then, in your table view controller, you set it like this if you're using static content:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
if let user = cell as? RecipeUsing {
user.recipe = recipe
}
return cell
}
You'll need a RecipeTitleImageCell with outlets to the UIImageView and UILabel. Something like this:
class RecipeTitleImageCell: UITableViewCell, RecipeUsing {
#IBOutlet var label: UILabel!
// UITableViewCell has an imageView property that's not an outlet 😭
#IBOutlet var myImageView: UIImageView!
var recipe: Recipe? {
didSet {
guard let recipe = recipe else { return }
label.text = recipe.title
myImageView.image = recipe.image
}
}
}
And for the ratings row, you'll want something like this:
class RecipeRatingsCell: UITableViewCell, RecipeUsing {
#IBOutlet var ratingControl: RatingControl!
#IBOutlet var label: UILabel!
var recipe: Recipe? {
didSet {
guard let recipe = recipe else { return }
ratingControl.rating = recipe.ratings.reduce(0, +) / recipe.Double(ratings.count)
if ratings.count < 5 { label.text = "Too few ratings" }
else { label.text = "\(ratings.count) ratings" }
}
}
}
I need to drag a Cell from CollectionView One and drop it to
CollectionView Two.
The Drag & Drop inside one CollectionView is no Problem, but how
can i get the Cell out of CollectionView One to CollectionView Two?
Any ideas? Any projects or frameworks that have already solved this problem?
Thanks for helping!
https://github.com/Ice3SteveFortune/i3-dragndrop Check this out - its a helper I'm working on to achieve just that. It also supports tableviews
UPDATE
I've recently released the second version of this codebase, called BetweenKit. Its now a fully-fledged drag-and-drop framework.
Hope it proves useful !
When you select the cell from the first collection view, remove it from this collection view, create a new view as copy of that cell place it as subview of the superview on top of all views. Make that view movable using pan gestures. As soon as you "drop" this intermediary cell, detect its position and add it to the current collection view.
Ok, here is the simplest flow ever for the following example:
Add UIGestureRecognizer for every of UICollectionView.
Connect every gesture recognizer with one method:
#IBAction func longPressGestureChanged(recognizer: UILongPressGestureRecognizer) { ... }
Within UIViewController add #IBOutlet for each of UICollectionView:
#IBOutlet var collectionViewGreen: UICollectionView!
#IBOutlet var collectionViewYellow: UICollectionView!
Implement gesture recognizer method to detect changes:
#IBAction func longPressGestureChanged(recognizer: UILongPressGestureRecognizer) {
let globalLocation = recognizer.locationInView(view)
if CGRectContainsPoint(collectionViewGreen.frame, globalLocation) {
//you cover green collection view
let point = view.convertPoint(globalLocation, toView: collectionViewGreen)
if let indexPath = collectionViewGreen.indexPathForItemAtPoint(point) {
//you cover cell in green collection view
} else {
//you do not cover any of cells in green collection view
}
} else if CGRectContainsPoint(collectionViewYellow.frame, globalLocation) {
//you cover yellow collection view
let point = view.convertPoint(globalLocation, toView: collectionViewYellow)
if let indexPath = collectionViewYellow.indexPathForItemAtPoint(point) {
//you cover cell in yellow collection view
} else {
//you do not cover any of cells in yellow collection view
}
} else {
//you do not cover any of collection views
}
}