Fetching JSON to ViewController and segue to 2nd ViewController - ios

I have fetched and parsed my Data from API in JSON format to FirstViewController and want to segue to the SecondViewController with the data selected at the concrete person from FirstViewController. The Problem is that I have an API with such example URL: https://www.example.com/api/?action=persons&ln=en which gives me all persons in this format:
[
{
"p_id": "4107",
"p_name": "Name1 Surname1",
"p_role": "Role1",
"general_image": "/imagedb/persons/4107/main/1.jpg"
},{
"p_id": "1978",
"p_name": "Name2 Surname2",
"p_role": "Role2",
"general_image": "/imagedb/persons/1978/main/1.jpg"
}, {...
...}
]
I am showing all these persons in my FirstViewController in CollectionView, which is working correctly, with images, names, roles. But also I need to show my SecondViewController with the data selected in FirstVC. My API for personByID is like this URL: https://www.example.com/api/?action=person&ln=en&personId=1978 which gives me JSON Data in this format:
{
"p_id": "1978",
"p_category": "[2]",
"p_name": "Name2 Surname2",
"p_role": "Role2",
"p_short": null,
"p_text": "long text...",
"p_date_start": "1922.02.05",
"p_date_end": "",
"p_profile_image": "1",
"p_status": "1",
"p_lang": "en",
"general_image": "/imagedb/persons/1978/main/1.jpg",
"photos": [
{
"image_id": "5",
"p_id": "1978",
"lang": "en",
"text": "some text...",
"general": "/imagedb/persons/1978/5.jpg",
"thumbs": "/imagedb/persons/1978/thumb/5.jpg"
},
{
"image_id": "7",
"p_id": "1978",
"lang": "en",
"text": "some text...",
"general": "/imagedb/persons/1978/7.jpg",
"thumbs": "/imagedb/persons/1978/thumb/7.jpg"
}
]
}
This is my Person Struct:
struct Person {
let id: String
let name: String
let role: String
fileprivate let imageURLString: String
var imageURL: URL? {
return URL(string: "https://www.example.com\(imageURLString)")
}
}
extension Person: JSONDecodable {
init(_ decoder: JSONDecoder) throws {
self.id = try decoder.value(forKey: "p_id")
self.name = try decoder.value(forKey: "p_name")
self.role = try decoder.value(forKey: "p_role")
self.imageURLString = try decoder.value(forKey: "general_image")
}
}
This is My FIRST VC:
import UIKit
class PersonListViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var personPages: [PagedResult<Person>] = [] {
didSet {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadPersons()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first else {
return
}
collectionView.deselectItem(at: selectedIndexPath, animated: animated)
}
var service = ExampleWebService()
private func loadPersons(page: Int = 0, resultsPerPage: Int = 5) {
service.persons(page: page, resultsPerPage: resultsPerPage) { (personPage) in
guard !self.loadedPersonPageNumbers.contains(page) else { return }
self.personPages.append(personPage)
self.updateLastIndexPath(personPage)
}
}
private(set) var lastIndexPath: IndexPath?
private func updateLastIndexPath(_ personPage: PagedResult<Person>) {
if personPage.results.isEmpty {
lastIndexPath = nil
}
else {
lastIndexPath = calculateLastIndexPath()
}
}
private func calculateLastIndexPath() -> IndexPath? {
guard let lastPage = personPages.last else { return nil }
let section = lastPage.pageNumber
let row = lastPage.results.count - 1
return IndexPath(row: row, section: section)
}
fileprivate var loadedPersonPageNumbers: [Int] {
return personPages.map { $0.pageNumber }
}
func person(at indexPath: IndexPath) -> Person? {
guard indexPath.section < personPages.count else {
return nil
}
guard indexPath.row < personPages[indexPath.section].results.count else {
return nil
}
let page = personPages[indexPath.section]
return page.results[indexPath.row]
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let personViewController = segue.destination as? PersonViewController,
let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first else {
return
}
personViewController.person = person(at: selectedIndexPath)
}
#IBAction func exitToPersonsView(segue: UIStoryboardSegue) {
}
}
extension PersonListViewController: UICollectionViewDelegate {
fileprivate var nextPageIndex: Int {
guard let lastPage = personPages.last else {
return 0
}
return lastPage.pageNumber.advanced(by: 1)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath == lastIndexPath {
loadPersons(page: nextPageIndex)
}
}
}
extension PersonListViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return personPages.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return personPages[section].results.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: PersonListCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! PersonListCollectionViewCell
cell.person = person(at: indexPath)
return cell
}
}
extension PersonListViewController: UINavigationBarDelegate {
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
And this is My Second VC:
import Foundation
import UIKit
final class PersonViewController: UIViewController {
#IBOutlet weak var imagesCollectionVIew: UICollectionView!
#IBOutlet weak var personRole: UILabel!
#IBOutlet weak var customNavigationBar: UINavigationBar!
var personImagesByID: [PagedResult<Person>] = [] {
didSet {
DispatchQueue.main.async {
self.imagesCollectionVIew.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadPersonImagesByID()
}
var person: Person?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let person = person {
title = person.name
personRole.text = person.role
}
customNavigationBar.topItem?.title = title
}
var service = ExampleWebService()
private func loadPersonImagesByID(page: Int = 0, resultsperPge: Int = 5) {
service.persons(page: page, resultsPerPage: resultsperPge) { (personPage) in
guard !self.loadedPersonPageNumbers.contains(page) else {
return
}
self.personImagesByID.append(personPage)
self.updateLastIndexPath(personPage)
}
}
private(set) var lastIndexPath: IndexPath?
private func updateLastIndexPath(_ personPage: PagedResult<Person>) {
if personPage.results.isEmpty {
lastIndexPath = nil
}
else {
lastIndexPath = calculateLastIndexPath()
}
}
private func calculateLastIndexPath() -> IndexPath? {
guard let lastPage = personImagesByID.last else {
return nil
}
let section = lastPage.pageNumber
let item = lastPage.results.count - 1
return IndexPath(row: item, section: section)
}
fileprivate var loadedPersonPageNumbers: [Int] {
return personImagesByID.map { $0.pageNumber }
}
func person(at indexPath: IndexPath) -> Person? {
guard indexPath.section < personImagesByID.count else {
return nil
}
guard indexPath.item < personImagesByID[indexPath.section].results.count else {
return nil
}
let page = personImagesByID[indexPath.section]
return page.results[indexPath.item]
}
}
extension PersonViewController: UICollectionViewDelegate {
fileprivate var nextPageIndex: Int {
guard let lastPage = personImagesByID.last else {
return 0
}
return lastPage.pageNumber.advanced(by: 1)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath == lastIndexPath {
loadPersonImagesByID(page: nextPageIndex)
}
}
}
extension PersonViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.imagesCollectionVIew.frame.height - 17, height: self.imagesCollectionVIew.frame.height - 17)
}
}
extension PersonViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return personImagesByID.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return personImagesByID[section].results.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: PersonViewCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImagesCollectionViewCell", for: indexPath) as! PersonViewCollectionViewCell
cell.person = person(at: indexPath)
return cell
}
}
extension PersonViewController: UINavigationBarDelegate {
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
Now my issue is that I am having a problem of how to write correctly my second struct PersonByID and when clicking on the person from First VC to show me data in Second VC from personByID URL Path.

Related

Error passing data to tableViewController

I have a problem. I'm making an app that consumes an API from themoviedb and saves the movies in the Realm. On the main screen I am using the CollectionViewController, and when I touch the movie it will go to details. However when it goes to details nothing appears and when I select another film it brings the previous film.
Home:
import UIKit
import RealmSwift
class HomeViewController: UIViewController,UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UISearchBarDelegate {
#IBOutlet weak var searchbar: UISearchBar!
#IBOutlet weak var ListOfMovies: UICollectionView!
var movies : Results<Moviess>!
let realm = try! Realm()
var detailsMovies = Moviess()
override func viewDidLoad() {
super.viewDidLoad()
print(Realm.Configuration.defaultConfiguration.fileURL!)
ListOfMovies.delegate = self
ListOfMovies.dataSource = self
searchbar.delegate = self
movies = realm.objects(Moviess.self)
getRequest()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
func getObjects(filter: String) -> Results<Moviess>{
movies = realm.objects(Moviess.self).filter("title CONTAINS[cd] %#",filter)
return movies
}
func getRequest(){
print(movies.count)
if movies.count < 1 {
RequestData.requisicao { (result) in
switch(result){
case .success(let detalhe):
self.ListOfMovies.reloadData()
print(detalhe)
case .failure(let error):
print(error)
}
}
}}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return movies.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "listOfMovies", for: indexPath) as! HomeCollectionViewCell
let infos = movies[indexPath.item]
cell.configurationMovie(movie: infos)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
detailsMovies = movies[indexPath.row]
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return UIDevice.current.userInterfaceIdiom == .phone ? CGSize(width: collectionView.bounds.width/2-20, height: 200) : CGSize(width: collectionView.bounds.width/3-20, height: 250)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
print(searchText)
if !searchText.isEmpty{
let filtro = getObjects(filter: searchText)
print(filtro)
ListOfMovies.reloadData()
}else{
movies = realm.objects(Moviess.self)
ListOfMovies.reloadData()
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "details")
{
let viewController = segue.destination as! DetailViewController
print(detailsMovies)
viewController.conta = detailsMovies
}
}
}
Details:
import UIKit
import RealmSwift
class DetailViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
var conta: Moviess!
let realm = try! Realm()
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return [conta].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "details", for: indexPath) as! DetailsTableViewCell
let infos = [conta][indexPath.item]
cell.prepare(movie: infos!)
return cell
}
}
Moviess
import RealmSwift
import UIKit
class Moviess: Object{
#objc dynamic var id = 0
#objc dynamic var title = ""
#objc dynamic var overview = ""
#objc dynamic var poster = ""
#objc dynamic var isFavorites = false
override class func primaryKey() -> String? {
return "id"
}
convenience init (id: Int){
self.init()
self.id = id
}
override class func indexedProperties() -> [String] {
return ["isFavorites"]
}
func insertMovieData(list: Moviess){
do {
let realm = try! Realm()
try! realm.write({ () -> Void in
realm.add(list)
})
} catch let error as NSError{
print("insert error : \(error)")
}
}
func togleFavorite(){
try? realm?.write{
isFavorites = !isFavorites
}
}
}
I have no idea where I might be giving this "bug". If someone can help and explain what is going on it will be very useful.
Your problem is that prepare(for:sender:) is called before collectionView(_:, didSelectItemAt:). prepare(for:sender:) should not rely on collectionView(_:, didSelectItemAt:).
Your prepare(for:sender:) method should look like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let cell = sender as? UICollectionViewCell,
let indexPath = ListOfMovies.indexPath(for: cell) else { return }
if (segue.identifier == "details") {
let viewController = segue.destination as! DetailViewController
viewController.conta = movies[indexPath.item]
}
}

Refresh pagination data in UITableView

I have implemented pagination in UITableView with WillDisplay method. Pagination process is working fine but if I need to reload a list on button click, then data is appending in the list. How to work around with this ?
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if (indexPath.row + 1 == playlistViewModel.numberOfRowsInSection()) {
if playlistViewModel.isReload != false {
pageIncrement += 1
playlistViewModel.playListingApi(enterView: false, page: pageIncrement)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
pageIncrement = 1
playlistViewModel.playListingApi(enterView: true, page: pageIncrement)
}
playlistViewModel.hitNextApiClosure = { [weak self] () in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self?.playlistViewModel.isReload = false
self?.playlistViewModel.playlistArray?.removeAll()
self?.playlistTableView.reloadData()
self?.pageIncrement = 1
self?.playlistViewModel.playListingApi(enterView: true, page: self?.pageIncrement ?? 1)
}
}
And ViewModel method is
func playListingApi(enterView: Bool, page: Int) {
self.isLoading = true
if (enterView){
playlistArray?.removeAll()
isReload = false
}
playlistService.getPlayList(page: "\(page)", limit: "20") { (result) in
self.isLoading = false
switch result {
case .success(let data):
self.playlist = data as? Playlist
guard let data = self.playlist?.data?.blocks else {
self.errorMessage = AlertMessage.somethingWentWrong
return
}
for playlistData in data {
self.playlistArray?.append(playlistData)
self.isReload = true
}
if (data.count == 0){
self.isReload = false
}
self.reloadTableBool = true
case .error(let message):
self.isReload = false
self.errorMessage = message
}
}
}
When you are reloading your tableView set page = 1 , empty tableView data source and reload tableView. Finally hitAPI for fresh set of data .
page = 1
mTblDataSource.removeAll()
mTableView.reloadData()
hitAPI()
Consider this one as a possible solution.
public class Pageable<T> {
public enum ObjectState {
case loading
case loaded
}
public private (set) var page: Int = 0
private var items: [T] = [T]()
private var state: ObjectState = .loading
private let itemsPerPage: Int
private var itemsReloaded: (() -> ())
public init(itemsPerPage: Int, items: [T] = [], itemsReloaded: #escaping (() -> ())) {
self.items = items
self.itemsPerPage = itemsPerPage
self.itemsReloaded = itemsReloaded
}
public var itemsCount: Int {
switch state {
case .loaded:
return items.count
case .loading:
return items.count + 1 // should be displaying cell with loading indicator
}
}
public var isLoaded: Bool {
return state == .loaded
}
public var isLoading: Bool {
return state == .loading
}
public func append(contentsOf items: [T]) {
state = items.count < itemsPerPage ? .loaded : .loading
self.items.append(contentsOf: items)
itemsReloaded()
}
public func incrementPage() {
page += 1
}
public func reset() {
page = 0
state = .loading
items = []
}
public func itemFor(_ index: Int) -> T? {
return items.indices.contains(index) ? items[index] : nil
}
}
struct Property {}
protocol SearchItemsDisplayLogic: class {
func reloadItemsViews()
}
protocol SearchItemsInteraction {
func loadMore(page: Int)
}
// MARK: View Related with UITableView example
lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(pullToRefresh(_:)), for: UIControl.Event.valueChanged)
return refreshControl
}()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return presenter.itemsCount
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
presenter.viewWillDisplayCellAt(indexPath.row)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if presenter.isLoadingCellNeeded(indexPath.row) {
return tableView.dequeueReusableCell(withIdentifier: "\(LoadingTableViewCell.self)", for: indexPath)
}
let cell = tableView.dequeueReusableCell(withIdentifier: "\(PropertyTableViewCell.self)", for: indexPath) as? PropertyTableViewCell
presenter.populate(cell: cell, indexPath: indexPath)
return cell ?? UITableViewCell(style: .default, reuseIdentifier: "\(UITableViewCell.self)")
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let property = presenter.property(indexPath.row) else {
return
}
}
protocol SearchItemsPresentation {
// MARK: Pagination logic
var itemsCount: Int { get }
// From the view.
func isLoadingCellNeeded(_ item: Int) -> Bool
func viewWillDisplayCellAt(_ item: Int)
func pullToRefresh()
func property(_ item: Int) -> Property?
// From the interactor.
func presentItems(items: [Property])
}
// MARK: - Presenter
class SearchItemsPresenter: SearchItemsPresentation {
weak var propertyDisplay: SearchItemsDisplayLogic?
lazy var interactor: SearchItemsInteraction? = {
return SearchItemsInteractor(presenter: self)
}()
var itemsCount: Int {
return pageable.itemsCount
}
private var pageable: Pageable<Property>!
init(viewController: SearchItemsDisplayLogic) {
self.propertyDisplay = viewController
pageable = Pageable(itemsPerPage: 15, itemsReloaded: {
self.propertyDisplay?.reloadItemsViews()
})
}
// TODO: presenter should not have UIKit!
func populate(cell: CellProtocol?, indexPath: IndexPath) {
guard let cell = cell else { return }
// populate
}
}
extension SearchItemsPresenter {
func property(_ index: Int) -> Property? {
return pageable.itemFor(index)
}
}
// MARK: Pageable
extension SearchItemsPresenter {
/// if it's loading show loading cell in the view.
func isLoadingCellNeeded(_ item: Int) -> Bool {
let isViewAtTheBottom = item == itemsCount - 1
return isViewAtTheBottom && pageable.isLoading
}
/// Called in `willDisplay` methods of the view.
func viewWillDisplayCellAt(_ item: Int) {
let isViewAtTheBottom = item == itemsCount - 1
if isViewAtTheBottom && pageable.isLoading {
interactor?.loadMore(page: pageable.page)
pageable.incrementPage()
}
}
func pullToRefresh() {
pageable.reset()
interactor?.loadMore(page: pageable.page)
pageable.incrementPage()
}
func presentItems(items: [Property]) {
pageable.append(contentsOf: items)
}
}
// MARK: - Interactor
class SearchItemsInteractor: SearchItemsInteraction {
private var presenter: SearchItemsPresentation
init(presenter: SearchItemsPresentation) {
self.presenter = presenter
}
func loadMore(page: Int) {
DispatchQueue.global(qos: .background).async {
sleep(1)
DispatchQueue.main.async {
// TODO: return some data
self.presenter.presentItems(items: [])
}
}
}
}

How to reload UIPageViewController to reload its views in Swift

I am using Page Controller embeded in Table VC. Table VC shows details of items and also contains collection view controller as embedded
one. So now when I select any Collection cell it should display the selected cell item details.
I am able to show everything for the new selected item but Page VC is not getting reloaded as per selected item images, it is still showing the last item images.
SO I'm stuck there. I am attaching the code for Page VC and Detail Table View
Please let me know the approach here to deal with it. Thanks in Advance.!!
//-----PAGE VC CODE------
import UIKit
import Firebase
protocol ProductImagesPageVCDelegate: class
{
func setupPageController(numberOfPages: Int)
func turnPageController(to index: Int)
}
class ProductImagesPageVC: UIPageViewController {
var product: Product!
weak var pageViewControllerDelegate: ProductImagesPageVCDelegate?
struct StoryBoard {
static let productImageVC = "ProductImageVC"
}
lazy var controllers: [UIViewController] = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var controllers = [UIViewController]()
if let imageLinks = self.product.imageLinks
{
for imageLink in imageLinks
{
let productImageVC = storyboard.instantiateViewController(withIdentifier: StoryBoard.productImageVC)
controllers.append(productImageVC)
}
}
self.pageViewControllerDelegate?.setupPageController(numberOfPages: controllers.count)
return controllers
}()
override func viewDidLoad() {
super.viewDidLoad()
// if #available(iOS 11.0, *) {
// contentInsetAdjustmentBehavior = .never
// } else {
// automaticallyAdjustsScrollViewInsets = false
// }
automaticallyAdjustsScrollViewInsets = false
dataSource = self
delegate = self
self.turnToPage(index: 0)
}
func turnToPage(index: Int)
{
let controller = controllers[index]
var direction = UIPageViewControllerNavigationDirection.forward
if let currentVC = viewControllers?.first
{
guard let currentIndex = controllers.index(of: currentVC) else {return}
if currentIndex > index
{
direction = .reverse
}
}
self.configuewDisplaying(viewController: controller)
setViewControllers([controller], direction: direction, animated: true, completion: nil)
}
func configuewDisplaying(viewController: UIViewController)
{
for (index, vc) in controllers.enumerated()
{
if viewController === vc {
if let productImageVC = viewController as? ProductImageVC
{
productImageVC.imageLink = self.product.imageLinks?[index]
self.pageViewControllerDelegate?.turnPageController(to: index)
}
}
}
}
}
extension ProductImagesPageVC: UIPageViewControllerDataSource
{
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let index = controllers.index(of: viewController)
{
if index < controllers.count - 1
{
return controllers[index + 1]
}
}
return controllers.first
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let index = controllers.index(of: viewController)
{
if index > 0
{
return controllers[index - 1]
}
}
return controllers.last
}
}
extension ProductImagesPageVC: UIPageViewControllerDelegate
{
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
self.configuewDisplaying(viewController: pendingViewControllers.first as! ProductImageVC)
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if !completed
{
self.configuewDisplaying(viewController: previousViewControllers.first as! ProductImageVC)
}
}
}
//-----------Tabel VIEW Controllers-----
import UIKit
class ProductDetailTVC: UITableViewController {
#IBOutlet var productImagesHeaderView: ProductImagesHeaderView!
var product: Product!
var products: [Product]?
private var selectedProduct: Product?
struct Storyboard {
static let productDetailCell = "ProductDetailCell"
static let buyButtonCell = "BuyButtonCell"
static let showProductDetailCell = "ShowProductDetailCell"
static let suggestionTableCell = "SuggestionTableCell"
static let showImagesPageVC = "ShowProductImagesPageVC"
static let showProductDetail = "ShowProductDetail"
}
override func viewDidLoad() {
super.viewDidLoad()
title = product.name
fetchProducts()
tableView.estimatedRowHeight = tableView.rowHeight
tableView.rowHeight = UITableViewAutomaticDimension
}
func fetchProducts()
{
Product.fetchProducts { (products) in
self.products = products
if let index = self.products?.index(where: {$0 === self.product}) {
self.products?.remove(at: index)
}
self.tableView.reloadData()
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 4
}
fileprivate func extractedFunc() -> UITableViewCell {
return UITableViewCell()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0
{
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.productDetailCell, for: indexPath) as! ProductDetailCell
cell.product = product
cell.selectionStyle = .none
return cell
} else if indexPath.row == 1
{
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.buyButtonCell, for: indexPath) as! BuyButtonCell
cell.product = product
cell.selectionStyle = .none
return cell
} else if indexPath.row == 2
{
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.showProductDetailCell, for: indexPath)
cell.selectionStyle = .none
return cell
}
else
{
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.suggestionTableCell, for: indexPath) as! SuggestionTableCell
//cell.selectionStyle = .none
return cell
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 3
{
return tableView.bounds.width + 68
}
else
{
return UITableViewAutomaticDimension
}
}
//Mark: - UITabeleViewDelegate
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == 3
{
if let cell = cell as? SuggestionTableCell
{
cell.collectionView.delegate = self
cell.collectionView.dataSource = self
cell.collectionView.reloadData()
cell.collectionView.isScrollEnabled = false
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == Storyboard.showImagesPageVC
{
if let imagesPageVC = segue.destination as? ProductImagesPageVC
{
imagesPageVC.product = product
imagesPageVC.pageViewControllerDelegate = productImagesHeaderView
}
}
}
}
//MARK: - UICollectionViewDataSource
extension ProductDetailTVC : UICollectionViewDataSource
{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SuggestionCollectionViewCell", for: indexPath) as! SuggestionCollectionViewCell
guard let products = products else {return cell}
let randomProduct = Int(arc4random_uniform(UInt32(products.count)))
cell.product = products[randomProduct]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let selectedProduct = products?[indexPath.item] else {return}
self.selectedProduct = selectedProduct
self.product = selectedProduct
navigationItem.title = selectedProduct.name
self.tableView.reloadData()
collectionView.reloadData()
self.reloadInputViews()
}
}
//MARK: - UICollectionViewDelegate
extension ProductDetailTVC : UICollectionViewDelegate
{
}
//MARK: - UICOLLECTIONVIEWDELEGATEFLOWLAYOUT
extension ProductDetailTVC : UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let layout = collectionViewLayout as? UICollectionViewFlowLayout
{
layout.minimumLineSpacing = 5.0
layout.minimumInteritemSpacing = 2.5
let itemWidth = (collectionView.bounds.width - 5.0) / 2.0
return CGSize(width: itemWidth, height: itemWidth)
}
return CGSize.zero
}
}
I assume you want it to change when you update the product property?
You'll need to add a didSet to the property which updates the currently displayed view controller.

UICollectionView Drag and Drop cell between collectionView

Hi I am using KDDragAndDropCollectionView for drag and drop feature between two three different collectionView. Everything is working fine, but I am not able to restrict the movement for particular case. I have three collectionView. The user can drop from A to B and B to C. But He cannot drah and drop from A to C or C to B or B to A.
Here is my code.
import SlideMenuControllerSwift
class MainViewController: NavigationBarViewController,KDDragAndDropCollectionViewDataSource {
#IBOutlet weak var inProgressView: UIView!
#IBOutlet weak var doneview: UIView!
#IBOutlet weak var toDoView: UIView!
#IBOutlet weak var doneCollectionView: UICollectionView!
#IBOutlet weak var inProgressCollectionView: UICollectionView!
#IBOutlet weak var toDoCollectionView: UICollectionView!
var drop: UIDropDown!
var toDoDataArray = [String]()
var inProgressDataArray = [String]()
var doneDataArray = [String]()
var dragAndDropManager : KDDragAndDropManager?
//MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
toDoDataArray.append("A")
toDoDataArray.append("B")
toDoDataArray.append("C")
inProgressDataArray.append("D")
doneDataArray.append("B")
doneDataArray.append("C")
// Do any additional setup after loading the view.
self.setUp()
self.setupDropDown()
self.dragAndDropManager = KDDragAndDropManager(canvas: self.view, collectionViews: [toDoCollectionView, inProgressCollectionView,doneCollectionView])
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
}
//MARK: - Private Method
func setupDropDown() {
drop = UIDropDown(frame: CGRect(x: 24, y: 80, width: 200, height: 40))
//drop.center = CGPoint(x: self.view.frame.midX, y: self.view.frame.midY)
drop.placeholder = "Select Month"
drop.options = ["Weekly", "Monthly", "Bi-Annual", "Annual"]
drop.didSelect { (option, index) in
self.drop.placeholder = option
print("You just select: \(option) at index: \(index)")
}
self.view.addSubview(drop)
}
func setUp()
{
//Setup Navigation Bar
self.menuIconImage = #imageLiteral(resourceName: "hamIco")
self.setNavigationBarButtonItem()
self.setNavigationBarItem()
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
//MARK: - Navigation Bar Button Action Method
//MARK: - Button Action Methods
/**
Button Action method. Gets called when the left navigation item
Parameters: sender - the button on which the event occurred
**/
#IBAction func leftBtnAction(sender: UIButton) {
self.toggleLeft()
}
// MARK: - UITableView DataSource abd Delegate
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("Step 1 called")
if collectionView.tag == 0 {
return toDoDataArray.count
}
else if collectionView.tag == 1 {
return inProgressDataArray.count
}
return doneDataArray.count
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("Step 2 called")
if collectionView.tag == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TO_DO_COLLECTION_CELL_ID, for: indexPath) as! ToDoCollectionViewCell
cell.isHidden = false
if let kdCollectionView = collectionView as? KDDragAndDropCollectionView {
if let draggingPathOfCellBeingDragged = kdCollectionView.draggingPathOfCellBeingDragged {
if draggingPathOfCellBeingDragged.item == indexPath.item {
cell.isHidden = true
}
}
}
return cell
}
else if collectionView.tag == 1 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: IN_PROGRESS_COLLECTION_CELL_ID, for: indexPath) as! InProgressCollectionViewCell
cell.isHidden = false
if let kdCollectionView = collectionView as? KDDragAndDropCollectionView {
if let draggingPathOfCellBeingDragged = kdCollectionView.draggingPathOfCellBeingDragged {
if draggingPathOfCellBeingDragged.item == indexPath.item {
cell.isHidden = true
}
}
}
return cell
}
else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DONE_COLLECTION_Cell_ID, for: indexPath) as! DoneCollectionViewCell
cell.isHidden = false
if let kdCollectionView = collectionView as? KDDragAndDropCollectionView {
if let draggingPathOfCellBeingDragged = kdCollectionView.draggingPathOfCellBeingDragged {
if draggingPathOfCellBeingDragged.item == indexPath.item {
cell.isHidden = true
}
}
}
return cell
}
}
// MARK : KDDragAndDropCollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, dataItemForIndexPath indexPath: IndexPath) -> AnyObject {
print("Step 3 called")
if collectionView.tag == 0 {
return toDoDataArray[indexPath.item] as AnyObject
}
else if collectionView.tag == 1 {
return inProgressDataArray[indexPath.item] as AnyObject
}
return doneDataArray[indexPath.item] as AnyObject
//return data[collectionView.tag][indexPath.item]
}
func collectionView(_ collectionView: UICollectionView, insertDataItem dataItem : AnyObject, atIndexPath indexPath: IndexPath) -> Void {
print("Step 4 called")
if collectionView.tag == 0 {
if let di = dataItem as? String {
toDoDataArray.insert(di, at: indexPath.item)
//data[collectionView.tag].insert(di, at: indexPath.item)
}
}
else if collectionView.tag == 1 {
if let di = dataItem as? String {
inProgressDataArray.insert(di, at: indexPath.item)
//data[collectionView.tag].insert(di, at: indexPath.item)
}
}
else{
if let di = dataItem as? String {
doneDataArray.insert(di, at: indexPath.item)
}
}
}
func collectionView(_ collectionView: UICollectionView, deleteDataItemAtIndexPath indexPath : IndexPath) -> Void {
print("Step 5 called")
if collectionView.tag == 0 {
toDoDataArray.remove(at: indexPath.item)
}
else if collectionView.tag == 1 {
inProgressDataArray.remove(at: indexPath.item)
}
else{
doneDataArray.remove(at: indexPath.item)
}
}
func collectionView(_ collectionView: UICollectionView, moveDataItemFromIndexPath from: IndexPath, toIndexPath to : IndexPath) -> Void {
print("Step 6 called")
if collectionView.tag == 0 {
let fromDataItem: String = toDoDataArray[from.item]
toDoDataArray.remove(at: from.item)
toDoDataArray.insert(fromDataItem, at: to.item)
}
else if collectionView.tag == 1 {
let fromDataItem: String = inProgressDataArray[from.item]
inProgressDataArray.remove(at: from.item)
inProgressDataArray.insert(fromDataItem, at: to.item)
}
else{
let fromDataItem: String = doneDataArray[from.item]
doneDataArray.remove(at: from.item)
doneDataArray.insert(fromDataItem, at: to.item)
}
}
func collectionView(_ collectionView: UICollectionView, indexPathForDataItem dataItem: AnyObject) -> IndexPath? {
print("Step 7 called")
if collectionView.tag == 0 {
if let candidate : String = dataItem as? String {
for item : String in toDoDataArray {
if candidate == item {
let position = toDoDataArray.index(of: item)! // ! if we are inside the condition we are guaranteed a position
let indexPath = IndexPath(item: position, section: 0)
return indexPath
}
}
}
}
else if collectionView.tag == 1 {
if let candidate : String = dataItem as? String {
for item : String in inProgressDataArray {
if candidate == item {
let position = inProgressDataArray.index(of: item)! // ! if we are inside the condition we are guaranteed a position
let indexPath = IndexPath(item: position, section: 0)
return indexPath
}
}
}
}
else{
if let candidate : String = dataItem as? String {
for item : String in doneDataArray {
if candidate == item {
let position = doneDataArray.index(of: item)! // ! if we are inside the condition we are guaranteed a position
let indexPath = IndexPath(item: position, section: 0)
return indexPath
}
}
}
}
return nil
}
}
//MARK: - Extension written for Slide the Menu
extension MainViewController : SlideMenuControllerDelegate {
func leftWillOpen() {
print("SlideMenuControllerDelegate: leftWillOpen")
}
func leftDidOpen() {
print("SlideMenuControllerDelegate: leftDidOpen")
}
func leftWillClose() {
print("SlideMenuControllerDelegate: leftWillClose")
}
func leftDidClose() {
print("SlideMenuControllerDelegate: leftDidClose")
}
func rightWillOpen() {
print("SlideMenuControllerDelegate: rightWillOpen")
}
func rightDidOpen() {
print("SlideMenuControllerDelegate: rightDidOpen")
}
func rightWillClose() {
print("SlideMenuControllerDelegate: rightWillClose")
}
func rightDidClose() {
print("SlideMenuControllerDelegate: rightDidClose")
}
}

UICollectionViewDelegateFlowLayout methods never called

I have a UICollectionView which lies upon a UITableViewCell. The cell is the delegate and datasource for the collection view. Everything is setup in a storyboard. The cell implements numberOfItemsInSection and cellForItemAtIndexPath methods and these methods are always called successfully. Also the cell implements methods from UICollectionViewDelegateFlowLayout protocol such as sizeForItemAtIndexPath. And these methods are never called. What may be a possible reason for that?
class SignUpFollowCategoriesCell : UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
#IBOutlet weak var categoryCollection: UICollectionView!
#IBOutlet weak var categoryLabel: UILabel!
#IBOutlet weak var followButton: UIButton!
func setChosen(chosen:Bool)
{
followButton.checked = chosen
}
var category:Category?
{
didSet
{
guard let confirmedFollowButton = self.followButton else
{
return
}
confirmedFollowButton.hidden = true
guard let category = self.category else
{
return
}
guard let controller = self.controller else
{
return
}
guard let sites = controller.allSites[category.id] else
{
return
}
confirmedFollowButton.hidden = sites.count == 0
}
}
weak var controller:SignUpFollowCategoriesController?
func setupWithController(control:SignUpFollowCategoriesController)
{
controller = control
guard let tmp = followButton else
{
return
}
tmp.addTarget(self, action: #selector(SignUpFollowCategoriesCell.clicked), forControlEvents: .TouchUpInside)
followButton.setStyleOptions(false, style: .ClearBackground, title: "+ Follow All")
followButton.setStyleOptions(true, style: .YellowBackground, title: "Following")
}
func clicked()
{
guard let control = controller else
{
return
}
guard let cat = category else
{
return
}
if control.categoryIsChosen(cat)
{
self.setChosen(false)
control.unfollowCategory(cat)
}else
{
self.setChosen(true)
control.followCategory(cat)
}
categoryCollection.reloadData()
}
private func updateFollowBtnStyle()
{
guard let control = controller else
{
return
}
guard let cat = category else
{
return
}
if control.categoryIsChosen(cat)
{
self.setChosen(true)
}else
{
self.setChosen(false)
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
if let tmp = self.category
{
if let sites = controller?.allSites[tmp.id]
{
return sites.count
}
}
return 0;
}
private func firstLetterCaps(str : String) -> String
{
var ret = str
ret.replaceRange(str.startIndex...str.startIndex, with: String(str[str.startIndex]).capitalizedString)
return ret
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
if let cell:SignupCategorySiteCell = collectionView.dequeueReusableCellWithReuseIdentifier("SignUpCategoryCollectionCell", forIndexPath: indexPath) as? SignupCategorySiteCell
{
cell.imageView.image = UIImage.imageWithColor(UIColor.slangBlueLight())
var slangSite : SlangSite?
if let tmpID = self.category?.id
{
let sites = controller?.allSites[tmpID]
if let site = sites![indexPath.row] as SlangSite?
{
if let url = site.details.siteProfileImageURL()
{
cell.imageView.af_setImageWithURL(url)
}
let siteName = firstLetterCaps(site.details.siteName)
cell.siteNameLabel.text = siteName
slangSite = site
}
}
if let control = controller,let site = slangSite
{
if control.siteIsChosen(site)
{
cell.followStyle()
}
else
{
cell.unfollowStyle()
}
}
cell.onCellClickedBlock = {[weak self,controller] in
if let site = slangSite, let control = controller
{
if control.siteIsChosen(site)
{
cell.unfollowStyle()
control.unfollowSite(site)
}
else
{
cell.followStyle()
control.followSite(site)
}
self?.updateFollowBtnStyle()
}
}
return cell
} else
{
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "DebugCell")
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("DebugCell", forIndexPath: indexPath)
cell.backgroundColor = UIColor.greenColor()
return cell
}
}
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: NSIndexPath) -> CGSize
{
return CGSizeMake(92, 112)
}
func collectionView( collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsZero
}
func collectionView( collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 100.0
}
}
Check method name according to the swift version:
public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
=======> sizeForItemAtIndexPath

Resources