Terminating app due to 'NSInternalInconsistencyException' error - swift

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert section 50 but there are only 1 sections after the update'
Hey everyone I have been stuck on this error all day. It crashes the app after the set Batch Load Limit of 50 cells. I believe I am populating the tableView incorrectly. Here is a snippet of the code directly working with sections in the table.
import UIKit
import Firebase
import Foundation
class NotificationViewController: GradientViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var GVX3: UIView!
var isVisible = false
var notifications: [GActionNotification] = []
//Pull to Refresh
lazy var refresher: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.tintColor = primaryColor
refreshControl.addTarget(self, action: #selector(requestData), for: .valueChanged)
return refreshControl
}()
//Pull to Refresh
#objc func requestData() {
print("requesting data")
let deadline = DispatchTime.now() + .milliseconds(856)
DispatchQueue.main.asyncAfter(deadline: deadline) {
self.refresher.endRefreshing()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
isVisible = true
NotificationService.shared.setSeen(notifications: notifications)
setNotificationsBadge()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
isVisible = false
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
configureTableView()
tableView.clipsToBounds = true
tableView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
if #available(iOS 13.0, *) {
tableView.backgroundColor = UIColor.systemBackground
} else {
// Fallback on earlier versions
tableView.backgroundColor = UIColor.white
}
tableView.allowsSelection = false
if #available(iOS 10.0, *) {
tableView.refreshControl = refresher
}else{
tableView.addSubview(refresher)
}
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
navigationController?.navigationBar.isTranslucent = true
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationController?.navigationBar.shadowImage = UIImage()
observeNotifications()
}
var loader: BatchCollectionLoader?
func requestMoreNotifications() {
loader?.getNextSnapshot(completion: { [weak self] (snapshot) in
NotificationService.shared.process(snapshot: snapshot, completion: { (notifications) in
guard notifications.count > 0 else { return }
guard let strongSelf = self else { return }
var indexSet = IndexSet.init(integer: strongSelf.notifications.count)
if notifications.count > 1 {
let range = (strongSelf.notifications.count...(strongSelf.notifications.count + notifications.count - 1))
indexSet = IndexSet.init(integersIn: range)
}
let shouldUpdate = strongSelf.notifications.count == 0
strongSelf.notifications.append(contentsOf: notifications)
if shouldUpdate {
strongSelf.tableView.reloadData()
} else {
strongSelf.tableView.beginUpdates()
strongSelf.tableView.insertSections(indexSet, with: .bottom)
strongSelf.tableView.endUpdates()
}
})
})
}
func observeNotifications() {
guard let current = GUser.current else { return }
loader = BatchCollectionLoader.init(collection: NotificationService.shared.notificationReference.document(current.uid).collection("notifications"))
loader?.getNextSnapshot(completion: { [weak self] (snapshot) in
NotificationService.shared.process(snapshot: snapshot, completion: { (notifications) in
guard let strongSelf = self else { return }
strongSelf.notifications = notifications.sorted(by: {$0.time > $1.time})
DispatchQueue.main.async {
strongSelf.tableView.reloadData()
}
if strongSelf.isVisible && isInForeground {
NotificationService.shared.setSeen(notifications: notifications)
}
strongSelf.setNotificationsBadge()
})
})
}
func setNotificationsBadge() {
UIApplication.shared.applicationIconBadgeNumber = notifications.filter({!$0.isSeen}).count
if notifications.filter({!$0.isSeen}).count > 0 {
self.tabBarController?.tabBar.items?[2].badgeValue = "\(notifications.filter({!$0.isSeen}).count)"
} else {
self.tabBarController?.tabBar.items?[2].badgeValue = nil
}
}
func configureTableView() {
tableView.register(UINib.init(nibName: LaunchNotificationCell.identifier, bundle: nil), forCellReuseIdentifier: LaunchNotificationCell.identifier)
tableView.register(UINib.init(nibName: GNotificationCell.identifier, bundle: nil), forCellReuseIdentifier: GNotificationCell.identifier)
tableView.rowHeight = 70
tableView.delegate = self
tableView.dataSource = self
}
}
extension NotificationViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notifications.count > 0 ? notifications.count : 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if notifications.count == 0 {
if let cell = tableView.dequeueReusableCell(withIdentifier: LaunchNotificationCell.identifier, for: indexPath) as? LaunchNotificationCell {
return cell
}
}
if let cell = tableView.dequeueReusableCell(withIdentifier: GNotificationCell.identifier, for: indexPath) as? GNotificationCell {
cell.configure(notification: notifications[indexPath.row])
cell.authorProfileTapped = { [weak self] uid in
guard let strongSelf = self else { return }
strongSelf.openProfile(id: uid)
}
cell.postTapped = { [weak self] postUid in
guard let strongSelf = self else { return }
let notification = strongSelf.notifications[indexPath.row]
if notification.action == .friendRequest {
strongSelf.openProfile(id: notification.user)
} else {
let alert = UIAlertController(title: nil, message: "Loading post...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
strongSelf.present(alert, animated: true, completion: nil)
PostService.shared.getPost(id: postUid, location: nil, completion: {post, err in
alert.dismiss(animated: true, completion: nil)
if let post = post {
guard let commentsVC = strongSelf.storyboard?.instantiateViewController(withIdentifier: "CommentsViewController") as? CommentsViewController else {return}
commentsVC.post = post
strongSelf.navigationController?.pushViewController(commentsVC, animated: true)
}
})
}
}
return cell
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if notifications.count == 0 {
return view.frame.height
}
return 70
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.section == tableView.numberOfSections - 1 &&
indexPath.row == tableView.numberOfRows(inSection: indexPath.section) - 1 {
requestMoreNotifications()
}
}
}
extension NotificationViewController: GPostHelperProtocol {}
extension NotificationViewController: ProfileHelperProtocol {}

Probably it was intended the following, but it would be better to describe what is supposed to be shown when there are notifications and not.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if notifications.count != 0 { // << here !!
if let cell = tableView.dequeueReusableCell(withIdentifier: LaunchNotificationCell.identifier, for: indexPath) as? LaunchNotificationCell {
return cell
}
}
return UITableViewCell() // << default ??

You are trying to insert 50 sections instead of 50 rows but your numberOfSections method claims there is only one section. That's why an exception is being thrown. Make sure you are inserting rows, not sections.

Related

Custom UISearchBar using TextField

I need to make custom UISearchBar using UITextField.
I try to make it myself, but nothing works. Please, write a code for UITextField, which will work like UISearchBar
It's code where I use UISearchBar, but I need to change to UITextField
class FoodViewController: UIViewController {
override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent }
let tableView = UITableView()
var foods = [FoodModel]()
var searchedFoods = [FoodModel]()
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
fetchFoods()
self.modalPresentationCapturesStatusBarAppearance = true
self.view.backgroundColor = UIColor.white
let controller = UIViewController()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.view.addSubview(self.tableView)
tableView.delegate = self
tableView.dataSource = self
let searchBar = UISearchBar(frame: CGRect(x: 0, y: 5, width: 350, height: 40))
searchBar.searchBarStyle = .minimal
searchBar.barStyle = .black
searchBar.delegate = self
self.view.addSubview(searchBar)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.updateLayout(with: self.view.frame.size)
}
func updateLayout(with size: CGSize) {
self.tableView.frame = CGRect.init(x: 0, y: 45, width: size.width, height: 400)
}
func fetchFoods() {
Database.database().reference().child("food").observe(.childAdded) { (snapshot) in
if let dict = snapshot.value as? [String: AnyObject] {
let newTitle = dict["title"] as! String
let exCell = FoodModel(title: newTitle)
self.foods.append(exCell)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
extension FoodViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if searching {
let food = searchedFoods[indexPath.item]
cell.textLabel?.text = food.title
} else {
let food = foods[indexPath.item]
cell.textLabel?.text = food.title
}
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchedFoods.count
} else {
return foods.count
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var titleOfFood = String()
if searching == true {
titleOfFood = searchedFoods[indexPath.row].title
print(titleOfFood)
} else {
titleOfFood = foods[indexPath.row].title
print(titleOfFood)
}
let alertController = UIAlertController(title: "Hello", message: "Message", preferredStyle: .alert)
let cancel = UIAlertAction(title: "Cancel", style: .default)
let save = UIAlertAction(title: "Save", style: .cancel) { (action) in
self.dismiss(animated: true, completion: nil)
}
alertController.addTextField { (textField) in
textField.keyboardType = .numberPad
textField.borderStyle = .roundedRect
textField.layer.borderColor = UIColor.clear.cgColor
textField.addConstraint(textField.heightAnchor.constraint(equalToConstant: 50))
textField.font = UIFont(name: "Roboto-Medium", size: 30)
// textField.cornerRadius = 8
}
alertController.addAction(save)
alertController.addAction(cancel)
self.present(alertController, animated: true)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
}
}
extension FoodViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.tableView {
SPStorkController.scrollViewDidScroll(scrollView)
}
}
}
extension FoodViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchedFoods = foods.filter({ $0.title.lowercased().prefix(searchText.count) == searchText.lowercased() })
searching = true
tableView.isHidden = false
tableView.reloadData()
let transitionDelegate = SPStorkTransitioningDelegate()
transitionDelegate.customHeight = 620
transitionDelegate.showIndicator = false
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
tableView.reloadData()
}
}
Basically (if i'm understanding you correctly) after you add your UITextField to the view, all you need to have is some method to trigger whenever the value of the UITextField changes.
Something like this:
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
And then:
func textFieldDidChange(_ textField: UITextField) {
let searchText = textField.text!
searchedFoods = foods.filter({ $0.title.lowercased().prefix(searchText.count) == searchText.lowercased() })
searching = true
tableView.isHidden = false
tableView.reloadData()
let transitionDelegate = SPStorkTransitioningDelegate()
transitionDelegate.customHeight = 620
transitionDelegate.showIndicator = false
}

Collectionview in TableviewCell, data repeat

I've a collectionview inside my resizable tablviewCells. Tableview has one cell in each 'n' number of sections. Datasource and delegate of collectionview are set to the tableviewCell. There is an API called on tablview's cellForRowAt, and the result is rendered on the collectionview for each cell. After the result is fetched, a delegate tells the tableview that collectionview is loaded and it should reload that cell without calling the API this time. But the problem is that my collectionview data is repeated after every 2 tableviewCells.
I know prepareForReuse should be override to get rid of cell reuse problems. I've implemented prepareForReuse in my collectionviewCells and set my label.text and imageView.image to nil. However i'm not sure what to add to prepareForReuse for my tableviewCell.
// TableView class
override func viewDidLoad() {
super.viewDidLoad()
storiesSections = [....]
tableView.register(UINib(nibName: "RWFeedTableViewCell", bundle: nil), forCellReuseIdentifier: "reuseIdentifier")
tableView.estimatedRowHeight = 1
tableView.rowHeight = UITableView.automaticDimension
}
override func numberOfSections(in tableView: UITableView) -> Int {
return storiesSections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! RWFeedTableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "reuseIdentifier") as! RWFeedTableViewCell
}
cell.delegate = self
cell.fetchData(feedSection: storiesSections[indexPath.section], indexPath: indexPath)
return cell
}
// delegate for tableview reload
func collectionViewDidEnd(updatedFeedSection: FeedSection, indexPath: IndexPath) {
storiesSections[indexPath.section] = updatedFeedSection
tableView.beginUpdates()
tableView.endUpdates()
}
// TableViewCell class
override func awakeFromNib() {
super.awakeFromNib()
initializeCode()
}
func initializeCode() {
// Set layout
self.collectionView.collectionViewLayout = RWWaterfallLayout2()
self.collectionView.register(UINib(nibName: "\(ImageThenTitleViewCell.self)", bundle: nil), forCellWithReuseIdentifier: kImageThenTitleCellID)
self.collectionView.register(UINib(nibName: "\(LeftImageCell.self)", bundle: nil), forCellWithReuseIdentifier: kLeftImageCellID)
self.collectionView.contentInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
self.collectionView.isScrollEnabled = false
self.collectionView.dataSource = self
self.collectionView.delegate = self
}
func fetchData(feedSection: FeedSection, indexPath: IndexPath) {
if feedSection.isLoadComplete {
return
}
if let catID = feedSection.categoryID {
let urlString = URL(string: <urlString>)
let urlRequest = URLRequest(url: urlString!)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: urlRequest) { (data, response, error) in
if error == nil {
} else {
print(error?.localizedDescription as Any)
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
print("success convert data to JSON")
DispatchQueue.main.async {
var updatedFeedSection = feedSection
updatedFeedSection.storiesArray? = (todo["data"]! as! Array)
updatedFeedSection.isLoadComplete = true
self.feedSection = updatedFeedSection
self.collectionView.reloadData()
self.collectionView.performBatchUpdates({
}, completion: { (complete) in
self.collectionViewHeightConstraint.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height + self.collectionView.contentInset.top + self.collectionView.contentInset.bottom
self.delegate?.collectionViewDidEnd(updatedFeedSection: updatedFeedSection, indexPath: indexPath)
})
}
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.feedSection == nil {
return 0
} else {
return (self.feedSection?.storiesArray?.count)!
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let indexForTen = indexPath.item%10
let story = self.feedSection?.storiesArray?[indexPath.item]
if indexForTen == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kImageThenTitleCellID, for: indexPath) as! ImageThenTitleViewCell
cell.setupData(story: story!)
return cell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kLeftImageCellID, for: indexPath) as! LeftImageCell
cell.setupData(story: story!)
return cell
}
}
override func prepareForReuse() {
super.prepareForReuse()
}
// Collectionview Cell
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setupData(story: Dictionary<String, Any>){
self.storyImage.image = nil // reset the image
let thumbImage = story["image"] as! Dictionary<String, String>
self.storyTitle.text = story["t"] as? String
self.storyImage.downloaded(from: (thumbImage["m"])!)
self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.borderWidth = 1
self.layer.cornerRadius = 8
}
override func prepareForReuse() {
super.prepareForReuse()
storyImage.image = nil
storyTitle.text = nil
}
// FeedSection struct
struct FeedSection {
var categoryID: String?
var storiesArray : [Dictionary<String, Any>]?
var isLoadComplete: Bool
init(categoryID: String) {
self.categoryID = categoryID
self.storiesArray = []
self.isLoadComplete = false
}
}
Currently the 3rd tableviewCell repeats the data of 1st tablviewCell. How to avoid repeating cell data?
Only problem was the feedSection object in TableViewCell. It should be initialized at the time fetchData() is called. And just reload the collectionView if isLoadComplete is true.
Also since isLoadComplete is set on completion handler of URLSession, I set it to true the time API is called. So the same api will not be called while waiting for response. Maybe an enum could be set for api call and api response events on FeedSection. But for now this works.
func fetchData(feedSection: FeedSection, indexPath: IndexPath) {
self.feedSection = feedSection
if self.feedSection.isLoadComplete {
self.collectionView.reloadData()
return
}
if let catID = feedSection.categoryID {
let urlString = URL(string: <urlString>)
let urlRequest = URLRequest(url: urlString!)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
self.feedSection.isLoadComplete = true
self.delegate?.collectionViewDidEnd(updatedFeedSection: self.feedSection, indexPath: indexPath)
let task = session.dataTask(with: urlRequest) { (data, response, error) in
if error == nil {
} else {
print(error?.localizedDescription as Any)
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
print("success convert data to JSON")
DispatchQueue.main.async {
self.feedSection.storiesArray? = (todo["data"]! as! Array)
self.feedSection.isLoadComplete = true
self.collectionView.reloadData()
self.collectionView.performBatchUpdates({
}, completion: { (complete) in
self.collectionViewHeightConstraint.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height + self.collectionView.contentInset.top + self.collectionView.contentInset.bottom
self.delegate?.collectionViewDidEnd(updatedFeedSection: self.feedSection, indexPath: indexPath)
})
}
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
}
Set UITableView.reloadData() after get the data.
And check if you set CollectionView.reloadData(), if yes then remove reloadData() of UICollectionView. Only set UITableView.reloadData()

Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value when using SearchController

Ive been working on my own version of an app from appCoda. I input the search bar the same way in the app but when I build and run the app I get an error at the searchController.searchResultsUpdater = self. When I comment that out the same error goes to, searchController.dimsBackgroundDuringPresentation = false.
import UIKit
import CoreData
class HousesTableViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchResultsUpdating {
var homes: [HomeMO] = []
var searchResults: [HomeMO] = []
var fetchResultController : NSFetchedResultsController<HomeMO>!
var searchController : UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
//Error is here↓
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController = UISearchController(searchResultsController: nil)
self.navigationItem.searchController = searchController
navigationController?.navigationBar.tintColor = UIColor(red: 94, green: 252, blue: 161)
if let customFont = UIFont(name: "Rubik-Medium", size: 40.0) {
navigationController?.navigationBar.largeTitleTextAttributes = [ NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: customFont ]
}
let fetchRequest: NSFetchRequest<HomeMO> = HomeMO.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "price", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate){
let context = (appDelegate.persistentContainer.viewContext)
fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
do{
try fetchResultController.performFetch()
if let fetchedObjects = fetchResultController.fetchedObjects{
homes = fetchedObjects
}
} catch {
print(error)
}
}
}
// 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
if searchController.isActive{
return searchResults.count
}else {
return homes.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dataCell", for: indexPath) as! HomesTableViewCell
let home = (searchController.isActive) ? searchResults[indexPath.row] : homes[indexPath.row]
cell.priceLabel.text = "$" + home.price!
if let homeImage = home.image{
cell.thumbnailImageView.image = UIImage(data: homeImage as Data)
}
cell.amountOfBathsLabel.text = (home.amountOfBaths! + "ba")
cell.amountOfBedsLabel.text = (home.amountOfBeds! + "bds")
cell.locationLabel.text = home.location?.uppercased()
cell.amountOfSquareFeetLabel.text = (home.amountOfSquareFeet! + "sqft")
cell.typeLabel.text = home.type
cell.layer.cornerRadius = 20
cell.layer.masksToBounds = true
// Configure the cell...
return cell
}
func filterContent(for searchText: String) {
searchResults = homes.filter({ (home) -> Bool in
if let type = home.type {
let isMatch = type.localizedCaseInsensitiveContains(searchText)
return isMatch
}
return false
})
}
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text{
filterContent(for: searchText)
tableView.reloadData()
}
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 61.0
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .fade)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
case .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .fade)
}
default:
tableView.reloadData()
}
if let fetchedObjects = controller.fetchedObjects {
homes = fetchedObjects as! [HomeMO]
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
#IBAction func unwindToHome(segue: UIStoryboardSegue){
dismiss(animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showHomeDetail"{
if let indexPath = tableView.indexPathForSelectedRow{
let destinationController = segue.destination as! HomeDetailViewController
destinationController.home = searchController.isActive ? searchResults[indexPath.row] : homes[indexPath.row]
}
}
}
}
Look at your code. The controller is initialized after setting the delegate. Always initialize an object before using it.
So change the order
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
self.navigationItem.searchController = searchController

TableView Swift with dynamic and fixed cells

I'm making an app that displays data from Firebase , I have a TV that has to show one fixed cell (to choose the type of data to display) and the other cell to display data from server this is the code:
import UIKit
import Firebase
import SwiftDate
class EventiTVC: UITableViewController {
let userID = FIRAuth.auth()?.currentUser?.uid
let ref = FIRDatabase.database().reference()
let utente = UserDefaults.standard
#IBOutlet weak var Menu_button: UIBarButtonItem!
var Eventi:[String] = []
override func viewDidLoad() {
super.viewDidLoad()
if self.revealViewController() != nil {
Menu_button.target = self.revealViewController()
Menu_button.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
let imageView = UIImageView(image: #imageLiteral(resourceName: "Hangover_Background"))
imageView.contentMode = .scaleAspectFill
self.tableView.backgroundView = imageView
self.tableView.tableFooterView = UIView()
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white,NSFontAttributeName: UIFont(name: "HelveticaNeue-Bold", size: 19)!]
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if self.revealViewController() != nil {
Menu_button.target = self.revealViewController()
Menu_button.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
self.navigationController?.navigationBar.barTintColor = UIColor.orange
self.navigationController?.navigationBar.tintColor = UIColor.white
self.navigationController?.navigationBar.titleTextAttributes = [ NSFontAttributeName: UIFont(name: "HelveticaNeue-Bold", size: 19)!,NSForegroundColorAttributeName : UIColor.white]
DeterminaInfoProprietario()
DeterminoLocali()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if section == 0{
return 1
}
else {
return Eventi.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell = Bundle.main.loadNibNamed("Event_Type", owner: self, options: nil)?.first as! Event_Type
return cell
}
else {
let cell = Bundle.main.loadNibNamed("Evento", owner: self, options: nil)?.first as! Evento
let EventoCorrente = Eventi[indexPath.row]
// scarico info Evento corrente ref.child("Eventi").child(EventoCorrente).observeSingleEvent(of: .value, with: { (snap) in
if snap.childrenCount != 0 {
let DatiEvento = snap.value as? NSDictionary
cell.Nome.text = DatiEvento?.value(forKey: "Nome") as! String!
cell.Locale.text = DatiEvento?.value(forKey: "Locale") as! String!
let urlcopertina = DatiEvento?.value(forKey: "Immagine Copertina") as! String!
cell.Copertina.aail_load(url: NSURL(string: urlcopertina!)!)
// per scaricare il colore , devo scaricare il locale
let locale = DatiEvento?.value(forKey: "Locale") as! String!
var Colore:UIColor? = nil
var Ombra:UIColor? = nil
self.ref.child("Locali").child(locale!).observeSingleEvent(of: .value, with: { (snap2) in
if snap2.childrenCount != 0 {
let DatiLocale = snap2.value as? NSDictionary
Colore = ColoriDaStringa(Colore: (DatiLocale?.value(forKey: "Colore Pagina"))! as! String)[0]
Ombra = ColoriDaStringa(Colore: (DatiLocale?.value(forKey: "Colore Pagina"))! as! String)[1]
cell.ViewC.backgroundColor = Colore!
cell.View_ombra.backgroundColor = Ombra!
}
})
}
else {
// Evento Cancellato? // gestire situazione
}
})
return cell
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat{
if indexPath.row == 0{
return 44
}else {
return 190
}
}
#IBAction func ritornaHome (Segue:UIStoryboardSegue){
}
}
I got 2 static cell and 1 dynamic cell, it should be the other way around, any idea why?
In your cellForRowAtIndexPath() method you should change your if statement from:
if (indexPath.row == 0)
to
if (indexPath.section == 0)
Modify these two functions to:
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Eventi.count+1
}
As we should go with latest features. You should go with Auto layout. Auto layout has in built feature for showing dynamic height of UITableView Cell. Refer below link if you want to learn.
Dynamic cell height UITableview
Otherwise here we have answer of issue in your code.
In numberOfSections() you have set wrong condition. Just replace
return 2
to
return 1
And
in numberOfRowsInSection() replace
if section == 0{
return 1
}
else {
return Eventi.count
}
to
return Eventi.count+1

Application is modifying the autolayout engine from a background thread Error

Im not sure why but I have a button and textfield on my viewController,
the button is not visible when the view appears but when I click where the button should be it then appears. The pre-populated text in the text box also does not appear.
I am being presented with the following warning (im not sure if its related):
2016-12-30 11:25:26.030 wellpleased[5462:776953] This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
How would I prevent this warning and have the elements appear correctly?
class events: UIViewController, UITableViewDelegate, UITableViewDataSource {
var rowID:String = ""
var value:String!
#IBOutlet var tableView: UITableView!
var tableData: [String] = []
var tableEventMonth: [String] = []
var tableEventDay: [String] = []
var tableEventCity: [String] = []
var tableEventLink: [String] = []
var tableEventID: [String] = []
#IBOutlet weak var tableTopConstraint: NSLayoutConstraint!
#IBOutlet weak var eventCodeView: UIView!
#IBAction func addButton(_ sender: Any) {
if self.eventCodeView.alpha == 1{
self.view.layoutIfNeeded()
UIView.animate(withDuration: 1, animations: {
self.view.layoutIfNeeded()
self.eventCodeView.alpha = 0
})
self.tableTopConstraint.constant = 1
UIView.animate(withDuration: 1) {
self.view.layoutIfNeeded()
}
}else{
self.view.layoutIfNeeded()
UIView.animate(withDuration: 1, animations: {
self.view.layoutIfNeeded()
self.eventCodeView.alpha = 1
})
self.tableTopConstraint.constant = 70
UIView.animate(withDuration: 1) {
self.view.layoutIfNeeded()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "vwTblCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "cell")
}
override func viewDidAppear(_ animated: Bool) {
getTableData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TblCell = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! TblCell
cell.lblCarName.text = self.tableData[indexPath.row]
cell.calendarDay.text = self.tableEventDay[indexPath.row]
cell.calendarMonth.text = self.tableEventMonth[indexPath.row]
cell.city.text = self.tableEventCity[indexPath.row]
let defaults = UserDefaults()
let event = defaults.string(forKey: "event")
if self.tableData[indexPath.row] == event {
cell.hereLabel.isHidden = false
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Row \(indexPath.row) selected")
rowID = tableEventID[indexPath.row]
self.performSegue(withIdentifier: "goAttendees", sender: self)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
func getTableData(){
let defaults = UserDefaults()
let userid = defaults.string(forKey: "id")
let url = NSURL(string: "https://www.asmserver.co.uk/wellpleased/backend/eventselect.php?userid=\(userid!)")
let task = URLSession.shared.dataTask(with: url as! URL) { (data, response, error) -> Void in
if let urlContent = data {
do {
if let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: []) as? [[String:AnyObject]] {
var i = 0
while i < jsonResult.count {
self.tableData.append(jsonResult[i]["eventname"]! as! String)
self.tableEventDay.append(jsonResult[i]["eventday"]! as! String)
self.tableEventMonth.append(jsonResult[i]["eventmonth"]! as! String)
self.tableEventCity.append(jsonResult[i]["city"]! as! String)
self.tableEventLink.append(jsonResult[i]["link"]! as! String)
self.tableEventID.append(jsonResult[i]["eventid"]! as! String)
i = i + 1
}
}
} catch {
print("JSON serialization failed")
}
} else {
print("ERROR FOUND HERE")
}
DispatchQueue.main.async(execute: { () -> Void in
self.tableView.reloadData()
})
self.tableView.isUserInteractionEnabled = true
}
task.resume()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if(segue.identifier == "goAttendees") {
let attendees = (segue.destination as! attendees)
attendees.value = rowID
}
}
}
You had better change UI only in the main thread. For swift3 refer to this post,
for more swift<2, 3 objective-c.