I been trying to get my head around passing data to various view controllers over the last few days.
I jumped in a bit too too deep at the start and got very confused.
Finally I think I have sorted. However I cannot see where I have gone wrong below. I am close but think i have gone blind from trying so much over the weekend. Looked at callbacks but I think delegates make more sense to me.
I have 2 view controllers. One with a UITableView and one where the data get inputted via text field.
I have no errors. However the input prints in viewcontroller 2 but does not show up in the UITableView.
Finding it hard to see what I've done wrong.
VC1:
import Foundation
import UIKit
private let reuseidentifier = "Cell"
//here
struct Contact {
var fullname: String
}
class ContactController: UITableViewController {
var contacts = [Contact]()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.title = "Contacts"
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(handleAddContact))
view.backgroundColor = .white
tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseidentifier)
}
#objc func handleAddContact () {
//here
let controller = AddContactController()
controller.delegate = self
self.present(UINavigationController(rootViewController: controller), animated: true, completion: nil)
}
//UITABLEVIEW
override func numberOfSections(in tableView: UITableView) -> Int {
return contacts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseidentifier, for: indexPath)
cell.textLabel?.text = contacts[indexPath.row].fullname
return cell
}
}
//here
extension ContactController: AddContactDelegate {
func addContact(contact: Contact) {
self.dismiss(animated: true) {
self.contacts.append(contact)
self.tableView.reloadData()
}
}
}
My VC2 where data gets inputted
import Foundation
import UIKit
//here
protocol AddContactDelegate {
func addContact(contact: Contact)
}
class AddContactController: UIViewController {
//here
var delegate: AddContactDelegate?
let textField: UITextField = {
let tf = UITextField()
tf.placeholder = "Full Name"
tf.textAlignment = .center
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDone))
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(handleCancel))
view.addSubview(textField)
textField.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textField.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
textField.widthAnchor.constraint(equalToConstant: view.frame.width - 64).isActive = true
textField.becomeFirstResponder()
}
//here
#objc func handleDone(){
print("done")
guard let fullname = textField.text, textField.hasText else {
print("handle error here")
return
}
let contact = Contact(fullname: fullname)
delegate?.addContact(contact: contact)
print(contact.fullname)
}
#objc func handleCancel(){
self.dismiss(animated: true, completion: nil )
}
}
There is no problem with your delegate implementation , You need to implement numberOfRowsInSection and return 1
override func numberOfSections(in tableView: UITableView) -> Int {
return contacts.count
}
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return 1
}
Or think if you really need this only
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return contacts.count
}
I believe protocol is implemented properly. Only thing I see is you are adding contacts after vc2 is dismissed as part of completion handler. Place the code before calling dismiss:
In func addContact()
contacts.append(contact)
tableView.reloadData()
dismiss()
Related
I use a custom search controller that has a tableView for showing the results,
the problem is when tapping the cancel button multiple times it dismisses the tabBarController.
But if i press it normally it acts as it should be.
class UICustomSearchController: UISearchController {
private var suggestedTableView: UITableView!
weak var suggestionDelegate: SearchSuggestionsDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
suggestedTableView = UITableView(frame: CGRect(x: 0, y: searchBar.frame.maxY,
width: view.frame.width,
height: view.frame.height - 70))
suggestedTableView.backgroundColor = UIColor.clear
suggestedTableView.tableFooterView = UIView()
view.subviews.forEach {
if $0.isKind(of: UITableView.self) {
$0.removeFromSuperview()
}
}
view.addSubview(suggestedTableView)
suggestedTableView.dataSource = self
suggestedTableView.delegate = self
suggestedTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
let tap = UITapGestureRecognizer(target: self, action: #selector(tableTapped))
suggestedTableView.addGestureRecognizer(tap)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
suggestedTableView.removeFromSuperview()
suggestedTableView = nil
dismiss(animated: false, completion: nil)
}
func reloadSuggestions() {
suggestedTableView.reloadData()
}
private func dismissView() {
searchBar.text = ""
suggestedTableView.removeFromSuperview()
dismiss(animated: false, completion: nil)
suggestionDelegate?.didDismissed()
}
// MARK: - Actions
#objc func tableTapped(tap:UITapGestureRecognizer) {
suggestedTableView.removeGestureRecognizer(tap)
let location = tap.location(in: suggestedTableView)
let path = suggestedTableView.indexPathForRow(at: location)
if let indexPathForRow = path {
tableView(suggestedTableView, didSelectRowAt: indexPathForRow)
} else {
dismissView()
}
}
}
extension UICustomSearchController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return suggestionDelegate?.suggestions().count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let list = suggestionDelegate?.suggestions() ?? []
cell.textLabel?.text = list[indexPath.row]
cell.textLabel?.textColor = UIColor.color(from: .blueTabBar)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let list = suggestionDelegate?.suggestions() ?? []
searchBar.text = list[indexPath.row]
searchBar.becomeFirstResponder()
suggestionDelegate?.didSelect(suggestion: list[indexPath.row])
}
}
And in the viewController that has search:
func initSearchViews() {
// Create the search controller and specify that it should present its results in this same view
searchController = UICustomSearchController(searchResultsController: nil)
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.barTintColor = UIColor.white
searchController.searchBar.tintColor = UIColor.color(from: .blueTabBar)
searchController.searchBar.setValue(Strings.cancel.localized, forKey:"_cancelButtonText")
searchController.suggestionDelegate = self
if let searchTextField = searchController!.searchBar.value(forKey: "searchField") as? UITextField {
searchTextField.placeholder = Strings.search.localized
}
// Make this class the delegate and present the search
searchController.searchBar.delegate = self
}
I tried putting this line in viewController but nothing happened:
definesPresentationContext = true
I have a UITabBarViewController with two tabs. I want to present a viewController fullscreen in one of the tabs. I have used the following code to do so.
let navCtrl = UINavigationController(rootViewController: eventViewController)
navCtrl.modalPresentationStyle = .fullScreen
self.navigationController?.present(navCtrl, animated: true)
It works. And EventViewController is fullscreen. However, when presenting another viewController in EventViewController, EventViewController is still fullscreen. But I want it to shrink in size and stack-up as it normally do( as in the image). In order to do so, I have changed modalPresentationStyle to overCurrentContext.
let navCtrl = UINavigationController(rootViewController: eventViewController)
navCtrl.modalPresentationStyle = .overCurrentContext
self.navigationController?.present(navCtrl, animated: true)
It does so, but it causes another problem: If I change tabs and dismiss EventViewController, the presenting viewController is black as described in this question (none of the answers was helpful).
Basically I want the EventController to be fullscreen but shrink in size when presenting another controller in it. How to do so?
Update
A simple project with the same issue.
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let ctrl = TabZeroViewController()
ctrl.tabBarItem.image = UIImage(named: "archived-task")
ctrl.tabBarItem.title = "One"
let test = TabOneViewController()
test.tabBarItem.image = UIImage(named: "Test")
test.tabBarItem.title = "Test"
let tabBarList = [ctrl, test ]
self.viewControllers = tabBarList.map {
let nav = UINavigationController(rootViewController: $0)
nav.interactivePopGestureRecognizer?.isEnabled = true
return nav
}
}
}
class TabZeroViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.view.backgroundColor = .white
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let ctrl = ModalTableViewController()
let nav = UINavigationController(rootViewController: ctrl)
nav.modalPresentationStyle = .fullScreen
self.navigationController?.present(nav, animated: true)
}
}
class ModalTableViewController: UITableViewController {
override func viewDidLoad() {
self.view.backgroundColor = .red
let button = UIButton()
button.setTitle("Cancel", for: .normal)
button.addTarget(self, action: #selector(dismissModal), for: .allEvents)
let item = UIBarButtonItem()
item.customView = button
self.navigationItem.leftBarButtonItem = item
self.tableView.dataSource = self
self.tableView.delegate = self
}
#objc func dismissModal() {
self.dismiss(animated: true, completion: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "Event"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let ctrl = EventViewController()
let nav = UINavigationController(rootViewController: ctrl)
nav.modalPresentationStyle = .overCurrentContext
self.navigationController?.present(nav, animated: true)
}
}
class TabOneViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
class EventViewController: UITableViewController {
override func viewDidLoad() {
self.view.backgroundColor = .red
let button = UIButton()
button.setTitle("Cancel", for: .normal)
button.addTarget(self, action: #selector(dismissModal), for: .allEvents)
let item = UIBarButtonItem()
item.customView = button
self.navigationItem.leftBarButtonItem = item
self.tableView.dataSource = self
self.tableView.delegate = self
}
#objc func dismissModal() {
self.dismiss(animated: true, completion: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "Event"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let ctrl = EventViewController()
let nav = UINavigationController(rootViewController: ctrl)
self.navigationController?.present(nav, animated: true)
}
}
Add this code in willConnectTo of SceneDelegate.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = TabBarController()
self.window = window
window.makeKeyAndVisible()
}
While you are on first tab, select a table cell to open the ModalTableViewController. And then change tabs and dismiss ModalTableViewController.
As for example project - presenting view over full screen hides TabBar. But I changed code a bit to propose working solution. Probably you would want to change it a bit, but I hope this will push you in good direction :)
It was actually needed to dismiss ModalTableViewController to avoid black screen.
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let ctrl = TabZeroViewController()
ctrl.tabBarItem.image = UIImage(named: "archived-task")
ctrl.tabBarItem.title = "One"
let test = TabOneViewController()
test.tabBarItem.image = UIImage(named: "Test")
test.tabBarItem.title = "Test"
let tabBarList = [ctrl, test ]
let viewControllers: [UIViewController] = tabBarList.map {
let nav = UINavigationController(rootViewController: $0)
nav.interactivePopGestureRecognizer?.isEnabled = true
nav.tabBarItem = $0.tabBarItem
return nav
}
self.setViewControllers(viewControllers, animated: false)
}
override var selectedViewController: UIViewController? {
get {return super.selectedViewController}
set {
if super.selectedViewController?.presentedViewController != nil {
super.selectedViewController?.dismiss(animated: false, completion: nil)
}
super.selectedViewController = newValue
}
}
}
class TabZeroViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.view.backgroundColor = .white
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let ctrl = ModalTableViewController()
let nav = UINavigationController(rootViewController: ctrl)
nav.modalPresentationStyle = .currentContext
self.present(nav, animated: true)
}
}
class ModalTableViewController: UITableViewController {
override func viewDidLoad() {
self.view.backgroundColor = .red
let button = UIButton()
button.setTitle("Cancel", for: .normal)
button.addTarget(self, action: #selector(dismissModal), for: .allEvents)
let item = UIBarButtonItem()
item.customView = button
self.navigationItem.leftBarButtonItem = item
self.tableView.dataSource = self
self.tableView.delegate = self
}
#objc func dismissModal() {
self.presentingViewController?.dismiss(animated: false, completion: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "Event"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let ctrl = EventViewController()
let nav = UINavigationController(rootViewController: ctrl)
nav.modalPresentationStyle = .fullScreen
self.navigationController?.present(nav, animated: true)
}
}
class TabOneViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
}
}
class EventViewController: UITableViewController {
override func viewDidLoad() {
self.view.backgroundColor = .red
let button = UIButton()
button.setTitle("Cancel", for: .normal)
button.addTarget(self, action: #selector(dismissModal), for: .allEvents)
let item = UIBarButtonItem()
item.customView = button
self.navigationItem.leftBarButtonItem = item
self.tableView.dataSource = self
self.tableView.delegate = self
}
#objc func dismissModal() {
self.dismiss(animated: true, completion: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "Event"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let ctrl = EventViewController()
let nav = UINavigationController(rootViewController: ctrl)
self.navigationController?.present(nav, animated: true)
}
}
Good luck!
Try this code to present screen modally:
func getImageFromView() -> UIImage {
let layer = UIApplication.shared.keyWindow?.layer
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(layer?.frame.size ?? CGSize.zero, false, scale)
if let context = UIGraphicsGetCurrentContext() {
layer?.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image ?? UIImage()
}
return UIImage()
}
/// This is the method to present screen modally
/// - parameter controller: controller instance on which screen will be presented
func presentScreenModally(controller: UIViewController, animated: Bool) {
let loginController = UIStoryboard.loadLoginViewController()//Get instance of controller form storyboard
loginController.bgTranParentImg = getImageFromView()
let bgImage = getImageFromView()
let presentationStyleViewController = UIStoryboard.loadPresentationStyleController()// This is another controller, which I am pasting below
presentationStyleViewController.bgimage = bgImage
presentationStyleViewController.loginController = loginController
presentationStyleViewController.addChild(loginController)
controller.view.window?.addSubview(presentationStyleViewController.view)
loginController.view.frame = presentationStyleViewController.containerView.bounds
presentationStyleViewController.containerView.addSubview(loginController.view)
let navigationController = UINavigationController(rootViewController: presentationStyleViewController)
navigationController.navigationBar.isHidden = true
navigationController.modalPresentationStyle = .fullScreen
controller.navigationController?.present(navigationController, animated: animated, completion: nil)
}
PresentationStyleViewController class:
class PresentationStyleViewController: UIViewController {
#IBOutlet var containerView: UIView!
#IBOutlet var containeTopConstraint: NSLayoutConstraint!
#IBOutlet var containerBottomConstraint: NSLayoutConstraint!
#IBOutlet var backgroundImage: UIImageView!
var bgimage: UIImage?
let topPadding: CGFloat = 30
var loginController: LoginViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.uiSetup()
}
override func viewDidAppear(_ animated: Bool) {
restorePopup()
}
/// Initial UI setup
func uiSetup() {
containeTopConstraint.constant = self.view.frame.size.height
backgroundImage.image = bgimage
}
#IBAction func panGesture(_ sender: UIPanGestureRecognizer) {
guard let piece = sender.view else {return}
let translation = sender.translation(in: piece.superview)
containeTopConstraint.constant = translation.y >= topPadding ? translation.y : topPadding
if sender.state == .ended || sender.state == .cancelled {
if containeTopConstraint.constant > self.view.frame.size.height/4 && translation.y > 0 {
self.dismissPopup()
} else {
self.restorePopup()
}
}
}
/// Dismisses popup and controller
func dismissPopup() {
containeTopConstraint.constant = self.view.frame.size.height
UIView.animate(withDuration: 0.3,
animations: {
self.view.layoutIfNeeded()
}, completion: { (_) in
self.loginController?.btnClick_cross(UIButton())
self.dismiss(animated: false)
})
}
/// Restores popup at initial position
func restorePopup() {
containeTopConstraint.constant = topPadding
UIView.animate(withDuration: 0.3,
animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}
my problem: I want to open some kind of Profil if a user pushes a Button in a Table-View Cell. The Cells Data is downloaded from Parse.
The idea is based on Instagram, if you click on the username-button on Insta the profile from the user who posted the image will open. I want to create the same code, but i can't create the code to get the user. Can you help me?
Heres some code:
import UIKit
import Parse
class HomeController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let reuseIdentifer = "FeedCell"
var delegate: HomeControllerDelegate?
var newCenterController: UIViewController!
let tableView = UITableView()
//Für Parse:
var users = [String: String]()
var comments = [String]()
var usernames = [String]()
var lastnames = [String]()
var imageFiles = [PFFileObject]()
var wischen: UISwipeGestureRecognizer!
var wischen2: UISwipeGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
getData()
configureNavigationBar()
configurateTableView()
wischen = UISwipeGestureRecognizer()
wischen.addTarget(self, action: #selector(handleMenuToggle))
wischen.direction = .right
wischen.numberOfTouchesRequired = 1
view.addGestureRecognizer(wischen)
wischen2 = UISwipeGestureRecognizer()
wischen2.addTarget(self, action: #selector(handleMenuToggle))
wischen2.direction = .left
wischen2.numberOfTouchesRequired = 1
view.addGestureRecognizer(wischen2)
}
#objc func handleMenuToggle() {
delegate?.handleMenuToggle(forMenuOption: nil)
}
#objc func showProfile() {
let vc: AProfileViewController!
vc = AProfileViewController()
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
func configureNavigationBar() {
navigationController?.navigationBar.barTintColor = .darkGray
navigationController?.navigationBar.barStyle = .black
navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: UIFont(name: "Noteworthy", size: 22)!, NSAttributedString.Key.foregroundColor: UIColor.white]
//navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
navigationItem.title = "Mobile Job Board"
navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "ic_menu_white_3x").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(handleMenuToggle))
navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "ic_mail_outline_white_2x").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(showCreateNewArticle))
}
//MARK: Table View
//skiped table view configuration
}
// - MARK: Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifer, for: indexPath) as! FeedCell
imageFiles[indexPath.row].getDataInBackground { (data, error) in
if let imageData = data {
if let imageToDisplay = UIImage(data: imageData) {
cell.postImage.image = imageToDisplay
}
}
}
cell.descriptionLabel.text = comments[indexPath.row]
cell.userButton.setTitle("\(usernames[indexPath.row]) \(lastnames[indexPath.row])", for: UIControl.State.normal)
cell.userButton.addTarget(self, action: #selector(showProfile), for: .touchUpInside)
return cell
}
//skiped
}
Thanks a lot!
Tom
The issue here is that your button works on a selector and it has no idea about the sender or where it was called from.
I would do this by creating a custom table view cell (e.g. FeedCell) which allows you to set a delegate (e.g. FeedCellDelegate). Set your class as the delegate for the cell and pass into the cell it's current indexPath. You can then return the indexPath in the delegate call.
Example: Note that code has been removed for simplicity and this code has not been tested. This is simply to guide you in the right direction.
View Controller
import UIKit
class HomeController: UIViewController {
// stripped additional information for example
func showProfile(_ username: String) {
let vc: AProfileViewController!
vc = AProfileViewController()
vc.username = username
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
}
extension HomeController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifer, for: indexPath) as! FeedCell
cell.delegate = self
cell.descriptionLabel.text = comments[indexPath.row]
cell.userButton.setTitle("\(usernames[indexPath.row]) \(lastnames[indexPath.row])", for: UIControl.State.normal)
cell.setIndex(indexPath)
return cell
}
}
extension HomeController: FeedCellDelegate {
func didPressButton(_ indexPath: IndexPath) {
let userName = usernames[indexPath.row]
showProfile(username)
}
}
Feed Cell
import UIKit
protocol FeedCellDelegate {
didPressButton(_ indexPath: IndexPath)
}
class FeedCell: UICollectionViewCell {
var delegate: FeedCellDelegate?
var indexPath: IndexPath
#IBOutlet weak var userButton: UIButton
setIndex(_ indexPath: IndexPath) {
self.indexPath = indexPath
}
#IBAction userButtonPressed() {
if(delegate != nil) {
delegate?.didPressButton(indexPath)
}
}
}
You can generically and in a type safe way get the parent responder of any responder with:
extension UIResponder {
func firstParent<T: UIResponder>(ofType type: T.Type ) -> T? {
return next as? T ?? next.flatMap { $0.firstParent(ofType: type) }
}
}
So:
Get the parent tableviewCell of your button in the target action function
Ask your tableview for the index path
Use the index path.row to index into your users array:
#objc func showProfile(_ sender: UIButton) {
guard let cell = firstParent(ofType: UITableViewCell.self),
let indexPath = tableView.indexPath(for: cell) else {
return
}
let user = users[indexPath.row]
... do other stuff here ...
}
I am making a simple tableview with a customCell. and a searchBar above. but getting a strange behavior from tableView. customCell is showing but above it defaultCell is showing as well and data is getting populated into the defaultCell though i am setting data on my customCell.
this is the output i am getting
https://i.imgur.com/xsTIsiz.png
if you look at it closely you will see my custom cell UI is showing under the default cell.
My custom cell:
https://i.imgur.com/J4UpRle.png
This is my code from viewcontroller
import UIKit
class SuraSearchController: UIViewController {
let searchController = UISearchController(searchResultsController: nil)
let reciters = [Reciter(name: "Abdul Basit Abdus Samad", downloadUrl: ""),
Reciter(name: "Abdul Rahman Al-Sudais", downloadUrl: ""),
Reciter(name: "Ali Bin Abdur Rahman Al Huthaify", downloadUrl: ""),
Reciter(name: "Mishary Rashid Alafasy", downloadUrl: ""),
Reciter(name: "Cheik Mohamed Jibril", downloadUrl: ""),
Reciter(name: "Mohamed Siddiq El-Minshawi", downloadUrl: ""),
Reciter(name: "Mahmoud Khalil Al-Hussary", downloadUrl: ""),
Reciter(name: "Ibrahim Walk (English Only)", downloadUrl: ""),
Reciter(name: "Abu Bakr Al Shatri", downloadUrl: "")]
var filteredReciters = [Reciter]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// definesPresentationContext = true
initNavBar()
initTableView()
// Do any additional setup after loading the view.
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
func initNavBar() {
// show navbar
self.navigationController?.setNavigationBarHidden(false, animated: true)
// set search bar delegates
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
// Customize Search Bar
searchController.searchBar.placeholder = "Search Friends"
let myString = "Cancel"
let myAttribute = [ NSAttributedStringKey.foregroundColor: UIColor.white ]
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).title = myString
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(myAttribute, for: .normal)
if #available(iOS 11.0, *) {
let scb = searchController.searchBar
scb.tintColor = UIColor.white
scb.barTintColor = UIColor.white
if let textfield = scb.value(forKey: "searchField") as? UITextField {
textfield.textColor = UIColor.blue
if let backgroundview = textfield.subviews.first {
// Background color
backgroundview.backgroundColor = UIColor.white
// Rounded corner
backgroundview.layer.cornerRadius = 10
backgroundview.clipsToBounds = true
}
}
}
// Set search bar on navbar
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
}
func initTableView() {
tableView.dataSource = self
tableView.delegate = self
tableView.register(UINib(nibName: "SuraSearchCell", bundle: nil), forCellReuseIdentifier: "SuraSearchCell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredReciters = reciters.filter({( reciter : Reciter) -> Bool in
return reciter.name.lowercased().contains(searchText.lowercased())
})
tableView.reloadData()
}
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
/*
// 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.
}
*/
}
extension SuraSearchController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
return filteredReciters.count
}
return reciters.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "SuraSearchCell", for: indexPath) as? SuraSearchCell {
let candy: Reciter
if isFiltering() {
candy = filteredReciters[indexPath.row]
} else {
candy = reciters[indexPath.row]
}
cell.textLabel!.text = candy.name
return cell
}
return UITableViewCell()
}
}
extension SuraSearchController: UISearchResultsUpdating {
// MARK: - UISearchResultsUpdating Delegate
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
And code of the tableview cell
import UIKit
class SuraSearchCell: UITableViewCell {
#IBOutlet weak var itemTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func configureCell(item: Reciter) {
itemTitle.text = item.name
}
}
cell.textLabel is default UITableViewCell property. you just need to set value to your customCell itemTitle label.
Replace cell.textLabel!.text = candy.name with cell. itemTitle!.text = candy.name like below.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "SuraSearchCell", for: indexPath) as? SuraSearchCell {
let candy: Reciter
if isFiltering() {
candy = filteredReciters[indexPath.row]
} else {
candy = reciters[indexPath.row]
}
cell. itemTitle!.text = candy.name
//OR
cell.configureCell(candy) // IF your candy is of Reciter type
return cell
}
return UITableViewCell()
}
There may be a mistake with reuse identifier name. Match the name with the used one in the code.
Let update cellForRowAtIndex as below
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "SuraSearchCell", for: indexPath) as? SuraSearchCell {
let candy: Reciter
if isFiltering() {
candy = filteredReciters[indexPath.row]
} else {
candy = reciters[indexPath.row]
}
cell.configureCell(candy)
return cell
}
return UITableViewCell()
}
I am trying to connect a storyboard which I made to my view controller file in my chat app.
What I did is...
1) Create a view controller file(swift).
2) Create a storyboard file and set view controller class as a custom class.
3) Connect my component(table view) in my storyboard to a view controller as an outlet.
Now, I got "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value" error message.
Code is the following.
import UIKit
class ChatViewController: UIViewController {
var _device_width: Int = 0
var _device_height: Int = 0
var _footer_size: Int = 0
var _backBtn: UIBarButtonItem!
var window: UIWindow?
var _toolBar: UIToolbar!
var bottomView: ChatRoomInputView!
var chats: [ChatEntity] = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
showNavigateBar()
setupUI()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override var canBecomeFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
return bottomView
}
func showNavigateBar() {
let backButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationItem.backBarButtonItem = backButtonItem
self.title = 'My App'
}
}
extension ChatViewController {
func setupUI() {
self.view.backgroundColor = UIColor(red: 113/255, green: 148/255, blue: 194/255, alpha: 1)
// ERROR happen on this line.
// "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value"
tableView.separatorColor = UIColor.clear
tableView.estimatedRowHeight = 10000
tableView.rowHeight = UITableViewAutomaticDimension
tableView.allowsSelection = false
tableView.keyboardDismissMode = .interactive
tableView.register(UINib(nibName: "YourChatViewCell", bundle: nil), forCellReuseIdentifier: "YourChat")
tableView.register(UINib(nibName: "MyChatViewCell", bundle: nil), forCellReuseIdentifier: "MyChat")
let chat1 = ChatEntity(text: "text1", time: "10:01", userType: .I)
let chat2 = ChatEntity(text: "text2", time: "10:02", userType: .You)
let chat3 = ChatEntity(text: "text3", time: "10:03", userType: .I)
chats = [chat1, chat2, chat3]
}
}
extension ChatViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.chats.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let chat = self.chats[indexPath.row]
if chat.isMyChat() {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyChat") as! MyChatViewCell
cell.clipsToBounds = true
// Todo: isRead
cell.updateCell(text: chat.text, time: chat.time, isRead: true)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourChat") as! YourChatViewCell
cell.clipsToBounds = true
cell.updateCell(text: chat.text, time: chat.time)
return cell
}
}
}
extension ChatViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath)
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 10
}
}
And I defined view controller as a custom class.
What should I do to work correct?
Please give me an advice.
ADD)
ChatViewController is called by another class file.
let chatViewController = ChatViewController()
self.navigationController?.pushViewController(chatViewController, animated: false)
And I believe #IBOutlet is connect correctly.
ChatViewController.swift
ChatViewController.storyboard
And I know the tableView is nil when this function was called.
Actually, I don't know much about the storyboard because I usually don't use storyboard. I prefer to create UI by the code. But now, I have to use storyboard file because I want to use a sample project of the chat system.
ADD2)
I think I may mistake the setting of the storyboard. I have no idea how to use navigation controller or entry point on the storyboard.
You should add delegate and datasource in your viewLoad() function.
override func viewDidLoad() {
super.viewDidLoad()
showNavigateBar()
tableView.delegate = self
tableView.dataSource = self
setupUI()
}
If error still exist after that, check your "Outlets" in your Storyboard.
I fixed the problem.
I modified call method.
let storyBoard : UIStoryboard = UIStoryboard(name: "ChatViewController", bundle:nil)
let chatViewController = storyBoard.instantiateViewController(withIdentifier: "ChatViewController")
self.navigationController?.pushViewController(chatViewController, animated: true)
Thank you for your kindness advice.