So i'm trying to implement a searchbar to search over different categories. After looking online a bit, I took a tutorial for UITableView and adapted it to my UICollectionView.
The searching works fine, as I am able to print the search results which go into the filteredCategories array.
Where I am having trouble is reloading the UICollectionView using the new filteredCategories array. I tried collectionView.reloadData() as well as collectionView.reloadSections(NSIndexSet(index: 1) as IndexSet) but with no success.
It seems as if the values are passed only once when the section is being built, and that I cannot change it.
Here is my code:
import UIKit
class ViewController: UIViewController {
var collectionView: UICollectionView!
let categories: [String] = ["Technology", "Science", "Entertainment", "Business", "Health", "Sports"]
var filteredCategories: [String] = ["none"]
var count = 0
var filteredCount = 6
var ready: [String] = ["none"]
var readyCount = 0
func filterIt() {
if isFiltering {
ready = filteredCategories
readyCount = filteredCategories.count
} else {
ready = categories
readyCount = categories.count
}
print("second", ready)
}
lazy var sections: [Section] = [
TitleSection(title: "NewsStand"),
// SearchSection(),
BasicGridSection(categories: ready, count: readyCount)
]
lazy var collectionViewLayout: UICollectionViewLayout = {
var sections = self.sections
let layout = UICollectionViewCompositionalLayout { (sectionIndex, environment) -> NSCollectionLayoutSection? in
return sections[sectionIndex].layoutSection()
}
return layout
}()
let searchController = UISearchController(searchResultsController: nil)
var isSearchBarEmpty: Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String) {
filteredCategories = categories.filter { (category: String) -> Bool in
print("filtered", filteredCategories)
return category.lowercased().contains(searchText.lowercased())
}
filterIt()
collectionView.reloadData()
setupCollectionView()
collectionView.reloadSections(NSIndexSet(index: 1) as IndexSet)
}
var isFiltering: Bool {
return searchController.isActive && !isSearchBarEmpty
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Compositional Layout"
self.view.backgroundColor = UIColor.white
self.title = "NewsStand"
setupCollectionView()
filterIt()
// 1
searchController.searchResultsUpdater = self
// 2
searchController.obscuresBackgroundDuringPresentation = false
// 3
searchController.searchBar.placeholder = "Search"
// 4
navigationItem.searchController = searchController
// 5
definesPresentationContext = true
}
func setupCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: collectionViewLayout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.backgroundColor = UIColor.white
collectionView.register(UINib(nibName: "TitleCell", bundle: .main), forCellWithReuseIdentifier: TitleCell.identifier)
collectionView.register(UINib(nibName: "GridCell", bundle: .main), forCellWithReuseIdentifier: GridCell.identifier)
collectionView.register(UINib(nibName: "SearchCell", bundle: .main), forCellWithReuseIdentifier: SearchCell.identifier)
self.view.addSubview(collectionView)
collectionView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
collectionView.reloadData()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
collectionView.reloadData()
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
sections.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
sections[section].numberOfItems
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
sections[indexPath.section].configureCell(collectionView: collectionView, indexPath: indexPath)
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let articlesView = storyboard?.instantiateViewController(withIdentifier: "articleViewController") as? ArticleViewController else {
print("error")
return
}
// set the post id for the comments
articlesView.set(index: indexPath.item)
// present(articlesView, animated: true, completion: nil)
self.navigationController!.pushViewController(articlesView, animated: true)
}
}
extension ViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
filterContentForSearchText(searchBar.text!)
}
}
and Section:
struct BasicGridSection: Section {
// TODO: create a constant for the title of the header of type String
var categories: [String]
var numberOfItems: Int
// TODO: create an initializer to set the title
init(categories: [String], count: Int) {
self.categories = categories
self.numberOfItems = count
}
func layoutSection() -> NSCollectionLayoutSection? {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.1), heightDimension: .fractionalHeight(0.8))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(0.4))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
let section = NSCollectionLayoutSection(group: group)
return section
}
func configureCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: GridCell.self), for: indexPath) as? GridCell
cell!.set(label:categories[indexPath.row])
return cell!
}
}
Thanks!
Related
In my view collection I added a search bar and it filtered the cells but when I click the cell it self the path didn't change the path remain the same as it was before the search .
When I click the cell, it should take you to a specific page. Excuse my poor code but I just started to learn swift. And my problem might be super easy and obvious so bare with me please
This is my code I'm using swift storyboard
import UIKit
import Firebase
import FirebaseStorage
class resViewViewController: UIViewController, UISearchBarDelegate, UISearchDisplayDelegate
{
#IBOutlet weak var background: UIImageView!
#IBOutlet weak var icon: UIImageView!
#IBOutlet weak var resL: UILabel!
#IBOutlet weak var collection: UICollectionView!
var resources:[resFile] = []
let db = Firestore.firestore()
//dummy data
#IBOutlet weak var searchBar: UISearchBar!
var searchActive : Bool = false
var filtered:[resFile] = []
override func viewDidLoad() {
super.viewDidLoad()
let nipCell = UINib(nibName: "resourceCellCollectionViewCell", bundle: nil)
//
collection.delegate = self
collection.dataSource = self
searchBar.delegate = self
///
collection.register(nipCell, forCellWithReuseIdentifier: "cell")
loadResources()
}
func loadResources(){
db.collection("Resources").getDocuments { querySnapshot, error in
if let e = error {
print("There was an issue retrieving data from fireStore. \(e)")
}else {
if let snapshotDocuments = querySnapshot?.documents{
for doc in snapshotDocuments{
let data = doc.data()
if let rName = data["ResName"] as? String, let aName = data["authorName"] as? String, let pName = data["pubName"] as? String, let desc = data["desc"] as? String, let urlName = data["url"] as? String {
let newRes = resFile(name: rName, author: aName, publisher: pName, desc: desc, urlString: urlName)
self.resources.append(newRes)
DispatchQueue.main.async {
self.collection.reloadData()
}
}
}
// DispatchQueue.main.async {
// self.collection.reloadData()
// }
}
}
}
}//end loadResources
//search
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchActive = true;
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchActive = false;
self.searchBar.endEditing(true)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchActive = false;
self.searchBar.endEditing(true)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchActive = false;
self.searchBar.endEditing(true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filtered = resources.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
if(filtered.count == 0){
searchActive = false;
} else {
searchActive = true;
}
self.collection.reloadData()
}
}//end of class
extension resViewViewController:UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let w = (UIScreen.main.bounds.size.width - 110)/2
return CGSize(width: w, height: 160) //154
}//end size
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if(searchActive) {
return filtered.count
} else {
return resources.count
}
}//end count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collection.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! resourceCellCollectionViewCell
if(searchActive) {
cell.name.text = filtered[indexPath.row].name
} else {
cell.name.text = resources[indexPath.row].name
}
return cell
}//end cell
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "si_resourceListToDetail", sender: indexPath)
}//end
}//extention
extension resViewViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "si_viewResToPost", let vc = segue.destination as? resPostViewController {
vc.delegate = self
} else if segue.identifier == "si_resourceListToDetail",
let vc = segue.destination as? detailedResViewController, let indexPath = sender as? IndexPath {
vc.resource = resources[indexPath.row]
}
}
}//extension
extension resViewViewController: resPostViewControllerDelegate {
func resPost(_ vc: resPostViewController, resource: resFile?, added: Bool){
vc.dismiss(animated: true) {
if added, let r = resource {
self.resources.append(r)
self.collection.reloadData()
}
}
}
}//extension
The indexPath will always be the same if you select the first visible cell in the UICollectionView. You must check the underlying data set to get the difference..
Also: In Swift it's convention to have capital first letter of classes and structs.
You must do something like this to achieve what you want:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item: ResFile
if searchActive {
item = filtered[indexPath.item]
} else {
item = resources[indexPath.item]
}
performSegue(withIdentifier: "si_resourceListToDetail", sender: item)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let item = sender as? ResFile else { return }
// Send item to destination view controller
}
I am trying this for a couple of days now and haven't achieved anything yet. What I am trying to do is when a user picks a User item form the ViewController class, I want to save it in Realm and show it in the CollectionView of the SavedInterestsViewController class. I use a delegate pattern as suggested in this post How to access and refresh a UITableView from another class in Swift, but unfortunately I still receive nil, I guess because the GC removed the collectionView outlet already right? (please correct me if I misunderstood it). However, how can I get this to work by using a delegate pattern? Here is my code, this is the class where the user Picks a new User-item:
protocol ViewControllerDelegate {
func didUpdate(sender: ViewController)
}
class ViewController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var collectionView: UICollectionView!
var delegate: ViewControllerDelegate?
let numberOfTweets = 5
let realm = try! Realm()
var image = UIImage()
var imageArray: [String] = []
var userArray: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CollectionCell")
searchBar.delegate = self
}
}
extension ViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
print("One second...")
let functionClass = NetworkingFunctions()
var loopCount = 0
functionClass.getWikipediaAssumptions(for: searchBar.text!) { [self] (articleArray) in
self.userArray.removeAll()
for x in articleArray {
functionClass.performWikipediaSearch(with: x, language: WikipediaLanguage("en")) { (user) in
self.userArray.append(user)
collectionView.reloadData()
loopCount += 1
}
}
}
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userArray.count
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionViewCell
let image = UIImage.init(data: userArray[indexPath.item].image as Data)
cell.userImage.image = image
cell.nameLabel.text = userArray[indexPath.item].name
cell.userImage.layer.borderColor = image?.averageColor?.cgColor
if userArray[indexPath.item].checked == false {
cell.checkmark.isHidden = true
} else {
cell.checkmark.isHidden = false
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if userArray[indexPath.item].checked == false {
userArray[indexPath.item].checked = true
collectionView.reloadData()
let newUser = User()
newUser.image = userArray[indexPath.item].image
newUser.name = userArray[indexPath.item].name
newUser.checked = true
try! realm.write {
realm.add(newUser)
}
self.delegate = SavedInterestsViewController()
self.delegate?.didUpdate(sender: self)
}
else {
userArray[indexPath.item].checked = false
collectionView.reloadData()
}
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width/3 - 10
let height = width
return CGSize(width: width, height: height)
}
}
class User: Object {
#objc dynamic var image: NSData = NSData()
#objc dynamic var name: String = ""
#objc dynamic var checked: Bool = false
}
... and this is the class where I want to show the selected item, after the User clicked on the 'Back' Button of the navigation controller of the ViewController class:
class SavedInterestsViewController: UIViewController, ViewControllerDelegate {
func didUpdate(sender: ViewController) {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
#IBOutlet weak var addButton: UIBarButtonItem!
#IBOutlet weak var collectionView: UICollectionView!
let realm = try! Realm()
var userArray: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CollectionCell")
fetchDataFromRealm()
}
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "SavedToNew", sender: self)
}
func fetchDataFromRealm() {
userArray.append(contentsOf: realm.objects(User.self))
collectionView.reloadData()
}
}
extension SavedInterestsViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionViewCell
let image = UIImage.init(data: userArray[indexPath.item].image as Data)
cell.userImage.image = image
cell.nameLabel.text = userArray[indexPath.item].name
cell.userImage.layer.borderColor = image?.averageColor?.cgColor
if userArray[indexPath.item].checked == false {
cell.checkmark.isHidden = true
} else {
cell.checkmark.isHidden = false
}
return cell
}
}
extension SavedInterestsViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width/3 - 10
let height = width
return CGSize(width: width, height: height)
}
}
in your code you have lines
self.delegate = SavedInterestsViewController()
self.delegate?.didUpdate(sender: self)
but it do mostly nothing - you set the delegate to newly created class, didUpdate it - but I don't see any use of it - you don't present it in any way.
If I understand you right - you have SavedInterestsViewController, from it - you open ViewController, do something and when back to SavedInterestsViewController. (I can be wrong with your flow - correct me if so)
In this flow - you have delegate property in ViewController, but it must be of type SavedInterestsViewController. And you have to set it to SavedInterestsViewController when you open ViewController from it. And later in ViewController you have to call didUpdate method of delegate.
Hi I want to do drag drop using uicollectionview. When performing drag and drop it is moving contents i want to do it like in the photo. I want the box to carry itself. For example; when I drag the photo to 4 I should leave the red area with full measurements. Swap photo 6 to photo 1 like taking photo 3 to the left. I have researched in uicollectionview so much but I can’t find anything like this. Please help me
import UIKit
final class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var cellIds = ["image 1","image 2","image 3","image 4","image 5","6","7"]
override func viewDidLoad() {
super.viewDidLoad()
let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
flowLayout.estimatedItemSize = CGSize(width: 200, height: 10)
let gestureRecognizer = UILongPressGestureRecognizer(target: self,
action: #selector(self.handleLongPress(gestureRecognizer:)))
collectionView.addGestureRecognizer(gestureRecognizer)
}
#objc func handleLongPress(gestureRecognizer: UILongPressGestureRecognizer) {
guard let view = gestureRecognizer.view else { return }
let location = gestureRecognizer.location(in: view)
switch gestureRecognizer.state {
case .began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: location) else { break }
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(location)
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
}
extension ViewController: UICollectionViewDataSource,
UICollectionViewDelegateFlowLayout
{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellIds.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SampleCell", for: indexPath) as! SampleCell
let text = cellIds[indexPath.item]
cell.label.text = text
return cell
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let text = cellIds.remove(at: sourceIndexPath.item)
cellIds.insert(text, at: destinationIndexPath.item)
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if (indexPath.row==0)
{
return CGSize(width: 190, height: 100)
}
if (indexPath.row==1)
{
return CGSize(width: 190, height: 100)
}
if (indexPath.row==2)
{
return CGSize(width: 190, height: 400)
}
if (indexPath.row==3)
{
return CGSize(width: 400, height: 200)
}
return CGSize(width: 0, height: 0)
}
}
final class SampleCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = self.systemLayoutSizeFitting(layoutAttributes.size)
var newFrame = layoutAttributes.frame
// note: don't change the width
newFrame.size.height = ceil(size.height)
layoutAttributes.frame = newFrame
return layoutAttributes
}
}
picture
You can use UICollectionViewDragDelegate,
For multiple sections, in order to drag to the end item, we should add an extra item when dragging.
Sample Code:
ViewController:
import UIKit
enum CellModel {
case simple(text: String)
case availableToDropAtEnd
}
class SecondController: UIViewController {
private lazy var cellIdentifier = "cellIdentifier"
private lazy var supplementaryViewIdentifier = "supplementaryViewIdentifier"
private lazy var sections = 10
private lazy var itemsInSection = 2
private lazy var numberOfElementsInRow = 3
private lazy var data: [[CellModel]] = {
var count = 0
return (0 ..< sections).map { _ in
return (0 ..< itemsInSection).map { _ -> CellModel in
count += 1
return .simple(text: "cell \(count)")
}
}
}()
override func viewDidLoad() {
super.viewDidLoad()
let collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.minimumLineSpacing = 5
collectionViewFlowLayout.minimumInteritemSpacing = 5
let _numberOfElementsInRow = CGFloat(numberOfElementsInRow)
let allWidthBetwenCells = _numberOfElementsInRow == 0 ? 0 : collectionViewFlowLayout.minimumInteritemSpacing*(_numberOfElementsInRow-1)
let width = (view.frame.width - allWidthBetwenCells)/_numberOfElementsInRow
collectionViewFlowLayout.itemSize = CGSize(width: width, height: width)
collectionViewFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout)
collectionView.backgroundColor = .white
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
collectionView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
collectionView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: cellIdentifier)
collectionView.register(SupplementaryView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: supplementaryViewIdentifier)
collectionView.dragInteractionEnabled = true
collectionView.reorderingCadence = .fast
collectionView.dropDelegate = self
collectionView.dragDelegate = self
collectionView.delegate = self
collectionView.dataSource = self
}
}
extension SecondController: UICollectionViewDelegate { }
extension SecondController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data[section].count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! CollectionViewCell
switch data[indexPath.section][indexPath.item] {
case .simple(let text):
cell.label?.text = text
cell.backgroundColor = .gray
case .availableToDropAtEnd:
cell.backgroundColor = UIColor.green.withAlphaComponent(0.3)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: supplementaryViewIdentifier, for: indexPath)
}
}
extension SecondController: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
let itemProvider = NSItemProvider(object: "\(indexPath)" as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = data[indexPath.section][indexPath.row]
return [dragItem]
}
func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
let itemProvider = NSItemProvider(object: "\(indexPath)" as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = data[indexPath.section][indexPath.row]
return [dragItem]
}
func collectionView(_ collectionView: UICollectionView, dragSessionWillBegin session: UIDragSession) {
var itemsToInsert = [IndexPath]()
(0 ..< data.count).forEach {
itemsToInsert.append(IndexPath(item: data[$0].count, section: $0))
data[$0].append(.availableToDropAtEnd)
}
collectionView.insertItems(at: itemsToInsert)
}
func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) {
var removeItems = [IndexPath]()
for section in 0..<data.count {
for item in 0..<data[section].count {
switch data[section][item] {
case .availableToDropAtEnd:
removeItems.append(IndexPath(item: item, section: section))
case .simple:
break
}
}
}
removeItems.forEach { data[$0.section].remove(at: $0.item) }
collectionView.deleteItems(at: removeItems)
}
}
extension SecondController: UICollectionViewDropDelegate {
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
let destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath {
destinationIndexPath = indexPath
} else {
// useless, just in case
let section = collectionView.numberOfSections - 1
let row = collectionView.numberOfItems(inSection: section)
destinationIndexPath = IndexPath(row: row, section: section)
}
switch coordinator.proposal.operation {
case .move:
reorderItems(coordinator: coordinator, destinationIndexPath:destinationIndexPath, collectionView: collectionView)
default:
break
}
}
func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
// made the above logic useless
if collectionView.hasActiveDrag, destinationIndexPath != nil {
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
else {
return UICollectionViewDropProposal(operation: .forbidden)
}
}
private
func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
let items = coordinator.items
if items.count == 1, let item = items.first,
let sourceIndexPath = item.sourceIndexPath,
let localObject = item.dragItem.localObject as? CellModel {
collectionView.performBatchUpdates ({
data[sourceIndexPath.section].remove(at: sourceIndexPath.item)
data[destinationIndexPath.section].insert(localObject, at: destinationIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
})
}
}
}
View:
import UIKit
class CollectionViewCell: UICollectionViewCell {
weak var label: UILabel?
override init(frame: CGRect) {
super.init(frame: frame)
clipsToBounds = true
let label = UILabel(frame: .zero)
label.contentMode = .scaleAspectFill
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
label.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
label.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
label.textAlignment = .center
label.textColor = .white
self.label = label
layer.borderWidth = 1
layer.borderColor = UIColor.white.cgColor
backgroundColor = .white
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func prepareForReuse() {
super.prepareForReuse()
label?.text = nil
backgroundColor = .white
}
}
class SupplementaryView: UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.blue.withAlphaComponent(0.7)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
github link
In my app using UIPageViewController to display full size images, and at the bottom of screen have thumbnail CollectionView each thumbnail is representing UIPageViewController full size image.My question is how to achieve paging of UIpageViewController when user select different thumbnail collection view cell?
if anyone know how to achieve this functionality , please help me with code or advice or links. Thanks
I believe I have to work with UICollectionView’s didSelectItemAtIndexPath method ,but not too sure how best to do it and would really appreciate help, please.
My Collection View File
protocol ThumbnailViewDelegate: class {
func didSelectThumbnail(at index: Int)
}
class ThumbnailView: UIView, UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout, PhotoCollectionViewModelPresenter{
weak var delegate: ThumbnailViewDelegate?
var assets: [PHAsset] = []
private let viewModel = PhotoCollectionViewModel()
//private var myViewModel: DetailViewModel!
private let imageDownloader = ImageDownloader(targetSize: CGSize(width: 80, height: 80 ))
//var selectedImage: UIImage?
lazy var thumbnailCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let view = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
view.translatesAutoresizingMaskIntoConstraints = false
view.isPagingEnabled = true
view.delegate = self
view.dataSource = self
view.isPrefetchingEnabled = false
view.register(ThumbnailCollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
let cellImageView = ThumbnailCollectionViewCell()
view.addSubview(cellImageView.imageView)
view.backgroundColor = UIColor.white
return view
}()
let imageView: ThumbnailCollectionViewCell = {
let view = ThumbnailCollectionViewCell()
view.contentMode = .scaleAspectFit
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(thumbnailCollectionView)
thumbnailCollectionView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
thumbnailCollectionView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
thumbnailCollectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
thumbnailCollectionView.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func propagateReload() {
thumbnailCollectionView.reloadData()
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return assets.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath as IndexPath) as! ThumbnailCollectionViewCell
cell.layer.masksToBounds = true
cell.layer.cornerRadius = 8
let image = assets[indexPath.item]
imageDownloader.download(asset: image) { [weak self] (image) in
self?.configureCell(indexPath: indexPath, image: image)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: 80.0, height: 80.0)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate?.didSelectThumbnail(at: indexPath.item)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
}
func configureCell(indexPath: IndexPath, image: UIImage) {
guard let cell = thumbnailCollectionView.cellForItem(at: indexPath) as? ThumbnailCollectionViewCell else {
return
}
cell.configure(image: image)
}
}
PageViewController File
class MainPageViewController: UIPageViewController {
var mainImageIndex: Int?
var pageViewModels: [DetailPageViewModel]!
var viewModel: DetailViewModel!
var collectionAssets: [PHAsset] = []
var selectedAsset: UIImage?
private var pendingIndex: Int?
// Collection View
lazy var collectionView: ThumbnailView = {
let view = ThumbnailView()
view.delegate = self
view.assets = self.collectionAssets
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
//MARK: - Create VC
lazy var pageViewControllersArray: [PageViewController] = {
return pageViewModels.map {
return PageViewController(viewModel: $0)
}
}()
var currentIndex:Int {
get {
return pageViewControllersArray.index(of: self.viewControllers!.first! as! PageViewController)!
}
set {
guard newValue >= 0,
newValue < pageViewControllersArray.count else {
return
}
let vc = pageViewControllersArray[newValue]
let direction:UIPageViewController.NavigationDirection = newValue > currentIndex ? .forward : .reverse
self.setViewControllers([vc], direction: direction, animated: true, completion: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
view.backgroundColor = UIColor.white
setViewControllers([pageViewControllersArray[mainImageIndex ?? 0]], direction: .forward, animated: true, completion: nil)
self.view.addSubview(collectionView)
setupCollectioViewAutoLayout()
}
func setupCollectioViewAutoLayout(){
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
collectionView.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
func viewWillAppear() {
super.viewWillAppear(true)
}
}
extension MainPageViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource, ThumbnailViewDelegate {
func didSelectThumbnail(at index: Int) {
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewController = viewController as? PageViewController else {return nil}
if let index = pageViewControllersArray.index(of: viewController){
if index > 0{
return pageViewControllersArray[index - 1]
}
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewController = viewController as? PageViewController else {return nil}
if let index = pageViewControllersArray.index(of: viewController){
if index < pageViewControllersArray.count - 1{
return pageViewControllersArray[index + 1]
}
}
return nil
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return pageViewControllersArray.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return currentIndex
}
}
When the select method is called, show another page view or page view controller on the whole screen.
I created a collection view with search bar. Rows are selectable on this collection view. Search bar is working perfectly when I search something and when I select a row. However when I clicked cancel button of search bar, the selected row is changing. For example, there are 3 row on the collection view. All of them are selectable. I'm searching for 3rd row and I'm selecting it. After the selecting this 3rd row, if I click to cancel button of search bar, selected row is changing and 1st row is being selected row. How can I handle with this issue?
import UIKit
class CollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UISearchControllerDelegate, UISearchBarDelegate {
#IBOutlet weak var collectionView: UICollectionView!
let searchController = UISearchController(searchResultsController: nil)
var things = [Things]()
var filteredThings = [Things]()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
CollectionViewController.instance = self
things = [
Things(name: "1", imageName: "firstImage", including: false),
Things(name: "2", imageName: "secondImage", including: false),
Things(name: "3", imageName: "thirdImage", including: false)
]
// Setup the Search Controller
self.searchController.searchResultsUpdater = self
self.searchController.delegate = self
self.searchController.searchBar.delegate = self
self.searchController.hidesNavigationBarDuringPresentation = false
self.searchController.dimsBackgroundDuringPresentation = true
self.searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search for tools and resources"
searchController.searchBar.sizeToFit()
searchController.searchBar.becomeFirstResponder()
self.navigationItem.titleView = searchController.searchBar
definesPresentationContext = true
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if isFiltering() {
return filteredThings.count
}
return things.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) as! CustomCollectionViewCell
let thing: Thing
if isFiltering() {
thing = filteredThings[indexPath.row]
} else {
thing = things[indexPath.row]
}
cell.imageView.image = UIImage(named: thing.imageName)
cell.labelView.text = thing.name
cell.layer.cornerRadius = 7
cell.imageView.layer.cornerRadius = 7
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
var thing: Things
if isFiltering() {
thing = filteredThings[indexPath.row]
} else {
thing = things[indexPath.row]
}
cell?.layer.cornerRadius = 5
cell?.layer.borderWidth = 3
cell?.layer.borderColor = myGreenTabBarColor.cgColor
thing.including = true
print(thing.name)
print(thing.including)
collectionView.allowsMultipleSelection = true
print("This cell is selected")
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
var thing: Things
if isFiltering() {
thing = filteredThings[indexPath.row]
} else {
thing = things[indexPath.row]
}
cell?.layer.cornerRadius = 5
cell?.layer.borderWidth = 3
cell?.layer.borderColor = UIColor.white.cgColor
thing.including = false
print(thing.including)
collectionView.allowsMultipleSelection = true
print("This cell is Deselected")
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
self.dismiss(animated: true, completion: nil)
collectionView.reloadData()
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredThings = things.filter({( thing : Things) -> Bool in
return thing.name.lowercased().contains(searchText.lowercased())
})
collectionView.reloadData()
}
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
}
extension CollectionViewController: UISearchResultsUpdating {
// MARK: - UISearchResultsUpdating Delegate
func updateSearchResults(for searchController: UISearchController) {
// TODO
filterContentForSearchText(searchController.searchBar.text!)
}
}
Ok, you have 3 rows ("a" , "b", "c")
Then you search for "c"
Now you have 1 row in table with "c"
This row has Indexpath(section = 0, row = 0)
Now you select row with Indexpath(section = 0, row = 0)
cancel search - again 3 rows
And the selected row is the row with Indexpath(section = 0, row = 0). Guess what item is this? ( "a" )
I suppose you should store selected items not indexpaths and make cells selected in cellforrow method