How to bring out the viewController after menu item is selected? - uitableview

Im trying to bring out the viewcontroller after i select an item in the menu. Currently, when I press something it just change the page, but it gets stuck on the right side. How do i bring that out.
This is the code that i wrote.
class ContainerVC: UIViewController {
var sideMenuOpen = false
#IBOutlet weak var mainView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(toggleSideMenu),
name: NSNotification.Name("ToggleSideMenu"),
object: nil)
}
#IBOutlet weak var sideMenuConstraint: NSLayoutConstraint!
#objc func toggleSideMenu() {
if sideMenuOpen {
sideMenuOpen = false
sideMenuConstraint.constant = -270
} else {
sideMenuOpen = true
sideMenuConstraint.constant = 0
}
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}
And this is what i wrote in my SideMenuVC
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
// NotificationCenter.default.post(name: NSNotification.Name ("ToggleSideMenu"), object: nil)
switch indexPath.row {
case 0: NotificationCenter.default.post(name: NSNotification.Name ("ShowProfile"), object: nil)
case 8: //signs out of Firebase
try? Auth.auth().signOut()
//signs out of Facebook
FBSDKAccessToken.setCurrent(nil)
let main = UIStoryboard(name: "Main", bundle: nil)
let second = main.instantiateViewController(withIdentifier: "loginPage")
self.present(second, animated: true, completion: nil)
default: break
//https://www.youtube.com/watch?v=Hl_Re_KLhcY
}
}
And this is what i wrote in my HomeVC
#IBAction func menuButton() {
print("Toggle Side Menu")
NotificationCenter.default.post(name: NSNotification.Name("ToggleSideMenu"), object: nil)
}
i would like the selected page to come out.

Just add NotificationCenter.default.post(name: NSNotification.Name ("ToggleSideMenu"), object: nil) to your didSelectRowAt

Related

tableview sent me an error when I tried to present a uiview in front of it

I'm new with Xcode and Swift, following a tutorial and I found a problem when I called a UIView in front of a tableview so the user can create something new
NOTE: I already tried what this link shows with no luck to resolve my issue
I am using Xcode 11.3.1 and Swift
This is my code
Channel Model
import Foundation
struct Channel : Decodable {
public private(set) var channelTitle: String!
public private(set) var channelDescription: String!
public private(set) var id: String!
}
Class ChannelCell
import UIKit
class ChannelCell: UITableViewCell {
// Outlets
#IBOutlet weak var channelName: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
self.layer.backgroundColor = UIColor(white: 1, alpha: 0.2).cgColor
} else {
self.layer.backgroundColor = UIColor.clear.cgColor
}
}
func configureCell(channel: Channel) {
let title = channel.channelTitle ?? ""
channelName.text = "#\(title)"
}
}
Channel View Controller
import UIKit
class ChannelVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
// Outlets
#IBOutlet weak var loginBtn: UIButton!
#IBOutlet weak var userImg: CircleImage!
#IBOutlet weak var tableView: UITableView!
#IBAction func prepareForUnwind(segue: UIStoryboardSegue) {}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self <<<<<<<<< here I get the error message ***
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
tableView.dataSource = self
self.revealViewController()?.rearViewRevealWidth = self.view.frame.size.width - 60
NotificationCenter.default.addObserver(self, selector: #selector(ChannelVC.userDataDidChange(_:)), name: NOTIF_USER_DATA_DID_CHANGE, object: nil)
}
override func viewDidAppear(_ animated: Bool) {
setupUserInfo()
}
When I pressed the add channel button comes the problem
#IBAction func addChannelPressed(_ sender: Any) {
if AuthService.instance.isLoggedIn {
let addChannel = ChannelVC()
addChannel.modalPresentationStyle = .custom
present(addChannel, animated: true, completion: nil)
} else {
performSegue(withIdentifier: TO_LOGIN, sender: nil)
}
}
#IBAction func loginBtnPressed(_ sender: Any) {
if AuthService.instance.isLoggedIn {
let profile = ProfileVC()
profile.modalPresentationStyle = .custom
present(profile, animated: true, completion: nil)
} else {
performSegue(withIdentifier: TO_LOGIN, sender: nil)
}
}
#objc func userDataDidChange(_ notif: Notification) {
setupUserInfo()
}
func setupUserInfo() {
if AuthService.instance.isLoggedIn {
loginBtn.setTitle(UserDataService.instance.name, for: .normal)
userImg.image = UIImage(named: UserDataService.instance.avatarName)
userImg.backgroundColor = UserDataService.instance.returnUIColor(components: UserDataService.instance.avatarColor)
} else {
loginBtn.setTitle("Login", for: .normal)
userImg.image = UIImage(named: "menuProfileIcon")
userImg.backgroundColor = UIColor.clear
}
}
// Protocols for UITableViewDataSource
// # of sections
// # rows in the section
// function to setup the cells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "channelCell", for: indexPath) as? ChannelCell {
I double check the reusable identifier is OK
let channel = MessageService.instance.channels[indexPath.row]
cell.configureCell(channel: channel)
return cell
} else {
return UITableViewCell()
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if MessageService.instance.channels.count == 0 {
tableView.setEmptyView(title: "Message!", message: "You donĀ“t have any channel, create a new one")
}
return MessageService.instance.channels.count
}
}
this is the view I want to show when I click on the addChannel function
view to present
and this is the debug area
debug area
You are making a very common mistake. The line
let addChannel = ChannelVC()
creates a new instance of the controller which is not the instance in the storyboard. Therefore the outlets are not connected and the code crashes.
Replace it with (adjust the identifier accordingly)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let addChannel = storyboard.instantiateViewController(withIdentifier: "ChannelVC") as! ChannelVC
or create a segue.

UITableView not refreshing after dismissing ViewController above

I have a ViewController being presented using Apple's new default ModalTransitionStyle. And when that ViewController is dismissed, the ViewController below (that has a TableView) doesn't update the table.
I have tried ViewWill/DidAppear but in both cases, I get the error "Unexpectedly found nil while unwrapping an optional Value" when trying to access the table.
I have checked what methods get called and I found that numberOfRowsInSection gets called and cellForRowAtIndexPath doesn't get called. The table is for sure visible. It is not height 0 and numberOfRowsInSection doesn't get 0 returned.
I made a quick test project to demonstrate what I mean.
Image when App Runs
When Popup button clicked
When i go back
import UIKit
var cells = 5
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var table: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cells
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "test")
return cell!
}
override func viewDidLoad() {
super.viewDidLoad()
table.reloadData()
// Do any additional setup after loading the view.
}
#IBAction func popup(_ sender: Any) {
performSegue(withIdentifier: "popover", sender: self)
}
}
import UIKit
class PopoverViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
cells += 1
// Do any additional setup after loading the view.
}
#IBAction func back(_ sender: Any) {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "vc") as! ViewController
self.dismiss(animated: true)
}
}
What should happen is that everytime i go back to the view 1 more cell is being displayed. I checked if the count "Cell" is being increased and it is. I just cant figure out how to reload the table.
Old answer:
DispatchQueue.main.async { [weak self] in
self?.table.reloadData()
}
New Answer:
One of the your mistakes is that
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "vc") as! ViewController
self.dismiss(animated: true, completion: {
vc.table.reloadData()
})
The vc is not your living Main Controller, you've to send living controller's reference.
By the way.
class PopoverViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
cells += 1
// Do any additional setup after loading the view.
}
#IBAction func back(_ sender: Any) {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "vc") as! ViewController
self.dismiss(animated: true, completion: {
vc.table.reloadData()
})
}
}
And your viewDidLoad method should be like:
override func viewDidLoad() {
super.viewDidLoad()
//table.reloadData()
// Do any additional setup after loading the view.
view.addSubview(table)
table.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
table.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
table.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
table.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
NotificationCenter.default.addObserver(self,
selector: #selector(notify),
name: .notifier,
object: nil)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.action,
target: self,
action: #selector(hh))
}
hh function is just for programmatically present Popover
#objc private func notify(notification: NSNotification){
//do stuff using the userInfo property of the notification object
print("notification has called")
DispatchQueue.main.async {
self.table.reloadData()
}
}
extension Notification.Name {
static let notifier = Notification.Name("notifier")
}
And change your cells variable like this:
var cells = 5{
didSet{
NotificationCenter.default.post(name: .notifier, object: nil)
}
}
I've created a Notification pattern to messaging between pages. It worked for me. When you increase cells instance Notification has notified and cell's has updated.
As i understand ViewController is parent, PopoverViewController is child. After child is dismissed, parent should update tableview.
viewDidLoad is called once for parent, it wont be called for dismissal of child.
So, you may define any delegate or closure for child view controller, which reload tableview at parent. you can call it at completion of dismiss.
dismiss(animated: Bool, completion: (() -> Void)?)
class PopoverViewController: UIViewController {
var onDismiss: (() -> Void)?
//...
}
// Also add this method to viewcontroller
class ViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier = "popover" {
guard let popoverVC = segue.destination as? PopoverViewController else {
return
}
popoverVC.onDismiss = { [unowned self] in
self.tableView.reloadData()
}
}
}
Also you don't need to create parent for dismissal.
let vc = UIStoryboard(name: "Main".. this line is unnecessary. this new vc is not shown so if you try to reach it, crash will occur due to outlets' nil value.

present ViewController when image inside a cell was pressed [duplicate]

This question already has answers here:
presentViewController from TableViewCell
(4 answers)
Closed 3 years ago.
I don't know how to present the viewController programmatically
I already finished creating a function for tapping of image. My only problem now is how to present the viewController. The image is inside the cell.
ItemsCell.swift
let tapGesture = UITapGestureRecognizer()
if let imageStr = obj.image {
item.optionImage.kf.setImage(with:URL(string:imageStr))
item.optionImage.isUserInteractionEnabled = true
tapGesture.addTarget(self, action: #selector(tappedImage))
item.optionImage.addGestureRecognizer(tapGesture)
} else {
item.optionImage.image = nil
}
The Function:
#objc func tappedImage() {
print("image tapped")
// let storyboard = UIStoryboard(name: "Main", bundle: nil)
// let controller = storyboard.instantiateViewController(withIdentifier: "AssetsViewController")
// controller.present(controller, animated: true, completion: nil)
}
Use Post Notification
class VC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(presentVC(_:)), name: NSNotification.Name("PresentVCNotificationName"), object: nil)
}
#objc
func presentVC(_ notification: Notification) {
let cell = notification.object as? YourCell
let storyboard = UIStoryboard(name: "Your Storyboard Name", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "Your Storyboard ID")
self.present(vc, animated: true, completion: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
class Cell: UITableViewCell {
#objc func tappedImage() {
NotificationCenter.default.post(name: NSNotification.Name("PresentVCNotificationName"), object: self)
}
}
In the interface builder tap on the viewController you want to show and make sure you defined a Storyboard Id. Create a class for that view controller, and make sure you have an outlet to the UIImageView that's supposed to show that image. Also make sure your class has a member of type UIImage.
// Assumed this class has Storyboard Id of: ImageViewerViewController
class ImageViewerViewController: UIViewController {
var imageView: UIImageView! // outlet from IB
var image: UIImage!
override func viewDidLoad() {
imageView.image = image;
}
}
In the view controller which contains the table:
class MainViewController: UIViewController {
.
.
.
// UITableViewDelegate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: ImageCell) as! YourC ImageCell ellClass
cell.containerVc = self
return cell
}
}
In the cell view:
class ImageCell: UITableViewCell {
weak var containerVc: UIViewController!
#objc func tappedImage() {
print("image tapped")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "ImageViewerViewController") as! ImageViewerViewController
controller.image = item.optionImage.image
containerVc.present(controller, animated: true, completion: nil)
}
}

NotificationCenter does not work between two views

I am trying to create a simple project with notification center
So the idea is to have two view controllers as shown on the picture
When I click on the button SelectionVC gets loaded and if I click
on one of the buttons on SelectionVC I wanna create notification and
go back to first view controller and change the label text, but so far I can not sort it out.
ViewController code
import UIKit
let mainNotificationName = Notification.Name("mainNotification")
let catalogueNotificationName =
Notification.Name("catalogueNotification")
class ViewController: UIViewController {
#IBOutlet weak var labelText: UILabel!
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(forName: mainNotificationName, object: nil, queue: nil) { (notification) in
print(notification)
self.labelText.text = "Main Notification"
}
NotificationCenter.default.addObserver(forName: catalogueNotificationName, object: nil, queue: nil) { (notification) in
print(notification)
self.labelText.text = "Catalogue Notification"
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func buttonTapped(_ sender: UIButton) {
let selectionVC = storyboard?.instantiateViewController(withIdentifier: "selectionVC") as! SelectionVC
present(selectionVC, animated: true, completion: nil)
}
}
SelectionVC code
import UIKit
class SelectionVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func mainButtonTapped(_ sender: UIButton) {
NotificationCenter.default.post(name: mainNotificationName, object: nil)
let targetVC = storyboard?.instantiateViewController(withIdentifier: "viewController") as! ViewController
self.present(targetVC, animated: true, completion: nil)
}
#IBAction func catalogueButtonTapped(_ sender: UIButton) {
NotificationCenter.default.post(name: catalogueNotificationName, object: nil)
let targetVC = storyboard?.instantiateViewController(withIdentifier: "viewController") as! ViewController
self.present(targetVC, animated: true, completion: nil)
}
}
Can you please help me sort it out.
Change these actions :
#IBAction func mainButtonTapped(_ sender: UIButton) {
NotificationCenter.default.post(name: mainNotificationName, object: nil)
let targetVC = storyboard?.instantiateViewController(withIdentifier: "viewController") as! ViewController
self.present(targetVC, animated: true, completion: nil)
}
#IBAction func catalogueButtonTapped(_ sender: UIButton) {
NotificationCenter.default.post(name: catalogueNotificationName, object: nil)
let targetVC = storyboard?.instantiateViewController(withIdentifier: "viewController") as! ViewController
self.present(targetVC, animated: true, completion: nil)
}
To :
#IBAction func mainButtonTapped(_ sender: UIButton) {
NotificationCenter.default.post(name: mainNotificationName, object: nil)
self.dismiss(animated: true, completion: nil)
}
#IBAction func catalogueButtonTapped(_ sender: UIButton) {
NotificationCenter.default.post(name: catalogueNotificationName, object: nil)
self.dismiss(animated: true, completion: nil)
}
And add notification observer in viewDidLoad() not in viewWillAppear(). And change text on Main thread (just use DispatchQueue.main.async {}).

Reload collection view data from another view class

I have two containers in a view. The top one has a collection view.
I want to update my collection view from a button when a button is hit from the below container. My button is also changing the value of an array, which my collection view uses.
I thought didSet would do the job but unfortunately did not work.
Top:
class TopViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var favoritesCV: UICollectionView!
var myFavorites = [] {
didSet {
self.favoritesCV.reloadData()
}
}
override func viewDidAppear(animated: Bool) {
myFavorites = favoritesInstance.favoritesArray
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myFavorites.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell : FavoritesCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! FavoritesCollectionViewCell
var myPath = myFavorites[indexPath.row] as! String
cell.myImage.image = UIImage(named: myPath)
return cell
}
}
Bottom:
class BottomViewController: UIViewController, UIScrollViewDelegate {
#IBAction func addFavorites(sender: AnyObject) {
favoritesInstance.favoritesArray.append("aaaa.jpg")
}
}
Storage Class:
class Favorites {
var favoritesArray:Array <AnyObject>
init(favoritesArray:Array <AnyObject>) {
self.favoritesArray = favoritesArray
}
}
var favoritesInstance = Favorites(favoritesArray:[])
I have added
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loadList:", name:"load", object: nil)
in my viewdidload of my collection view class.
also added a selector which reloads my data when it is called by the Notification Center
func loadList(notification: NSNotification){
self.favoritesCV.reloadData()
}
and for the other class where the button is pressed:
NSNotificationCenter.defaultCenter().postNotificationName("load", object: nil)
Swift 3:
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name:NSNotification.Name(rawValue: "load"), object: nil)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
Swift 4:
1st class:
NotificationCenter.default.post(name: NSNotification.Name("load"), object: nil)
Class with collectionView:
in viewDidLoad():
NotificationCenter.default.addObserver(self, selector: #selector(loadList(notification:)), name: NSNotification.Name(rawValue: "load"), object: nil)
and function:
#objc func loadList(notification: NSNotification) {
self.collectionView.reloadData()
}
Great! I was looking for this. In Swift 3 the code is slightly different. In collectionviewcontroller:
NotificationCenter.default.addObserver(self, selector: #selector(RoundCollectionViewController.load), name:NSNotification.Name(rawValue: "reload"), object: nil)
and in the other one:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)

Resources