I have a collection view controller. In collectionView cell I have label which I made clickable to push to the nextViewController.
I know that problem in navigationController. But I'm new in swift so can't fix. Hope you guys can help me.
Here's my SceneDelegate:
let layout = UICollectionViewFlowLayout()
// Create the root view controller as needed
let nc = UINavigationController(rootViewController: HomeController(collectionViewLayout: layout))
let win = UIWindow(windowScene: winScene)
win.rootViewController = nc
win.makeKeyAndVisible()
window = win
and my label:
let text = UILabel()
text.text = "something"
text.isUserInteractionEnabled = true
self.addSubview(text)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(PopularCellTwo.labelTapped))
text.addGestureRecognizer(gestureRecognizer)
}
#objc func labelTapped() {
let nextVC = NextViewController()
self.navigationController?.pushViewController(nextVC, animated: true)
print("labelTapped tapped")
}
I also added screenshot. When I click on "Something" It should go next page.
[1]: https://i.stack.imgur.com/4oYwb.png
You can use delegate or closure to do this
class ItemCollectionViewCell: UICollectionViewCell {
var onTapGesture: (() -> ())?
}
Then in your function you do
#objc func labelTapped() {
onTapGesture?()
}
And in your controller
class HomeController: UICollectionViewController {
//...
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = // dequeue cell
cell.onTapGesture = { [unowned self] in
let nextVC = NextViewController()
self.navigationController?.pushViewController(nextVC, animated: true)
}
return cell
}
}
self.navigationController?.pushViewController(nextVC, animated: true)
what self are you referring to ? because you cant make push in child class
you have HomeController i assume its your parent controller .
just try to debug what self is this could attempt by debugging or debug by condition
print (self)
if (self.isKind(of: YourParentController.self)) {
// make push
}
or try to check , see if navigationcontroller somehow has nil value
Here is how you do it using closures. I've created a closure parameter in UICollectionViewCell sub-class. When the label gesture target is hit I call the closure which then executed the navigation in HomeController.
class HomeController: UICollectionViewController {
//...
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = // dequeue cell
cell.labelTap = { [weak self] in
guard let self = self else { return }
let nextVC = NextViewController()
self.navigationController?.pushViewController(nextVC, animated: true)
print("navigated")
}
return cell
}
}
class CollectionViewCell: UICollectionViewCell {
var labelTap: (() -> Void)?
#objc func labelTapped() {
print("labelTapped tapped")
labelTap?()
}
}
Related
I want to add the feature to double tap a given message bubble.
Code so far:
#objc func doubleTap(gesture: UITapGestureRecognizer) {
print("double tap called")
}
func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle {
let tapGesture = UITapGestureRecognizer(target: messagesCollectionView, action: #selector(doubleTap(gesture:)))
tapGesture.numberOfTapsRequired = 2
view.addGestureRecognizer(tapGesture)
let sender = message.sender
if sender.senderId == selfSenderEmail?.senderId {
// Self Message
return .bubbleTailOutline(.blue, .bottomRight, .curved)
}
// More if statements for groupchat color coding
return .bubbleTailOutline(.darkGray, .bottomLeft, .curved)
}
I get this Error Thread 1: "-[MessageKit.MessagesCollectionView doubleTapWithGesture:]: unrecognized selector sent to instance 0x7f94c7112a00".
If you think my post isn't clear in anyway, please let me know so I can clarify it. Thank you
You are adding UITapGestureRecognizer on view from MessagesViewController, which will attach it to the same controller view for each cell.
I suppose we are trying to get a double-tap for message cell and in order to achieve that, add UITapGestureRecognizer in the cell that you are trying to get the double tap on.
Suppose, we have DoubleTappableCell, which can be a text cell, media cell, or any other message.
class DoubleTappableCell: MessageContentCell {
lazy var doubleTapRecognizer: UITapGestureRecognizer = {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(doubleTapped))
tapGesture.numberOfTapsRequired = 2
return tapGesture
}()
var onDoubleTapped: () -> Void = {}
override init(frame: CGRect) {
super.init(frame: frame)
addGestureRecognizer(doubleTapRecognizer)
}
#objc func doubleTapped() {
onDoubleTapped()
}
}
This cell will be returned from the MessageCollectionView.
public override func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell: VideoCell = collectionView
.dequeueReusableCell(indexPath: index) else { return UICollectionViewCell() }
cell.data = anyData
cell.onDoubleTapped = {
print("DoubleTapped")
}
}
Another alternate but improved approach would be to use one UITapGestureRecognizer from ViewController to detect the indexpath of the touched cell, get the relevant cell from that index, and execute actions and view updates based on that.
From First ViewController I press a Button and open a SecondViewController that view a collection of Image, when I tap on image the view show a full screen image and return at the collection View. Can I pass the image to the first (starting) viewcontroller to use it as a background?
class SecondViewController: UIViewController {
#IBAction func returnHome(_ sender: UIButton) {
//self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "backToHome", sender: nil)
}
var images = ["bg13", "bg11", "bg6", "bg10", "bg12", "bg5", "bg3", "bg4", "bg2", "bg7", "bg8", "bg9", "bg1", "bg14"]
// ...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let imageViewCollection = UIImageView(image:UIImage(named: images [indexPath.item]))
imageViewCollection.frame = self.view.frame
imageViewCollection.backgroundColor = .black
imageViewCollection.contentMode = .top
imageViewCollection.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreenImage))
imageViewCollection.addGestureRecognizer(tap)
imageViewCollection.addGestureRecognizer(tap)
imageViewCollection.isUserInteractionEnabled = true
self.view.addSubview(imageViewCollection)
}
#objc func dismissFullscreenImage(_ sender: UITapGestureRecognizer) {
sender.view?.removeFromSuperview()
}
}
You can use Delegate to pass the image to the first view controller.
Write this protocol on the top of the Second View Controller.
protocol SecondViewControllerDelegate: NSObject {
func sendToFirstViewController(image: UIImage)
}
Then create a variable inside Second View Controller
class SecondViewController: UIViewController {
....
weak var customDelegate: SecondViewControllerDelegate?
....
}
Call this from collectionView:didSelectItemAt:indexPath method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let image = UIImage(named: images [indexPath.item])
customDelegate?.sendToFirstViewController(image: image)
...
}
In the First viewController, in where you instantiate the second view controller, put this line
secondVc.customDelegate = self
then create an extension to implement the delegate method
extension FirstViewController: SecondViewControllerDelegate {
func sendToFirstViewController(image: UIImage){
// use this image as your background
}
}
Basically I have a CollectionView inside a tableviewcell and Here's the method I'm using to push to another viewcontroller.
Here's the code :
class recipeRelated: UITableViewCell,UICollectionViewDelegate,UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let resultViewController = storyBoard.instantiateViewController(withIdentifier: "recipeContainerView") as! recipeContainerView
self.window?.rootViewController?.show(resultViewController,sender: self)
}
}
Thank you in advance!
Use this extension:
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
This will give any view access to it's first parent controller. So you can use it like this:
self.parentViewController?.show(resultViewController, sender: self)
i'm learning Dependency Injection and created an app by MVP.
I could inject presenters for a few VCs in AppDelegate using storyboard.
But now I created CustomTableViewCell with .xib that has UIButton on the cell so that I would like to process the detail of it in the presenter file.
I tried to create UINib(nibName~ in AppDelegate but when the presenter in CustomTableViewCell is called, it is nil.
I heard that an instance of a cell called in 'AppDelegate' is going to be disposed of. However I don't know any ways not to make presenter nil.
AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let mainTabVC = UIStoryboard(name: "MainTab", bundle: nil).instantiateInitialViewController() as! MainTabViewController
let userDefault = UserDefault()
let presenter = MainTabPresenter(view: mainTabVC, udManager: userDefault)
mainTabVC.inject(presenter: presenter, userDefaultManager: userDefault)
let addTabVC = UIStoryboard(name: "AddTab", bundle: nil).instantiateInitialViewController() as! AddTabViewController
let alertHandller = AlertHandller()
let addPresenter = AddTabPresenter(view: addTabVC, mainView: mainTabVC, alertHandller: alertHandller)
addTabVC.inject(presenter: addPresenter)
let settingTabVC = UIStoryboard(name: "SettingTab", bundle: nil).instantiateInitialViewController() as! SettingTabViewController
let settingPresenter = SettingTabPresenter(view: settingTabVC)
settingTabVC.inject(presenter: settingPresenter, alertHandller: alertHandller)
let vcs = [mainTabVC, addTabVC, settingTabVC]
let mainTabBar = UIStoryboard(name: "MainView", bundle: nil).instantiateInitialViewController() as! MainTabBarController
mainTabBar.setViewControllers(vcs, animated: false)
// no probs until here (inject functions work)
// this is disposed??
let listCell = UINib(nibName: "ListCell", bundle: nil).instantiate(withOwner: ListCell.self, options: nil).first as! ListCell
let cellPresenter = ListCellPresenter(view: mainTabVC)
listCell.inject(presenter: cellPresenter)
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = mainTabBar
window?.makeKeyAndVisible()
return true
}
VC that tableViewDataSource is written
class MainTabViewController: UIViewController {
private let shared = Sharing.shared
private var presenter: MainTabPresenterInput!
private var userDefaultManager: UserDefaultManager!
func inject(presenter: MainTabPresenterInput, userDefaultManager: UserDefaultManager) {
self.presenter = presenter
self.userDefaultManager = userDefaultManager
}
override func viewDidLoad() {
super.viewDidLoad()
tableViewSetup()
}
func tableViewSetup(){
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: "ListCell", bundle: nil), forCellReuseIdentifier: "ListCell")
}
}
extension MainTabViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shared.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ListCell") as! ListCell
if let item = presenter.item(row: indexPath.row) {
cell.configure(item: item)
}
return cell
}
}
customTableViewCell
class ListCell: UITableViewCell {
private let shared = Sharing.shared
private var presenter: ListCellPresenterInput!
func inject(presenter: ListCellPresenterInput) {
self.presenter = presenter
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func favButtonTapped(_ sender: Any) {
let row = self.tag
switch shared.items[row].fav {
case true:
favButton.setImage(UIImage(named: "favIconNon"), for: .normal)
case false:
favButton.setImage(UIImage(named: "favIcon"), for: .normal)
}
presenter.countFavIcon(rowAt: row) // this presenter is nil
}
func configure(item: Item) {
・・・
}
}
cell presenter
protocol AddTabPresenterInput {
func addButtonTapped(item item: Item?)
}
protocol AddTabPresenterOutput: AnyObject {
func clearFields()
func showAlert(alert: UIAlertController)
}
final class AddTabPresenter: AddTabPresenterInput {
private weak var view: AddTabPresenterOutput!
private weak var mainView: MainTabPresenterOutput!
private weak var alertHandller: AlertHandllerProtocol!
let shared = Sharing.shared
init(view: AddTabPresenterOutput, mainView: MainTabPresenterOutput, alertHandller: AlertHandllerProtocol) {
self.view = view
self.mainView = mainView
self.alertHandller = alertHandller
}
func addButtonTapped(item item: Item?) {
print("called")
mainView.updateView()
}
}
How Can I solve the issue that presenter is nil?
Hopefully some of you would help me out.
Thank you.
UITableView uses reusable cells, you can find a lot of info in google, here is one of the articles
https://medium.com/ios-seminar/why-we-use-dequeuereusablecellwithidentifier-ce7fd97cde8e
So creating an instance of UITableViewCell in AppDelegate you're doing nothing, it is immediately dispose
Create presenter
// somewhere in your MainTabViewController
let cellPresenter = ListCellPresenter(view: self)
To inject presenter you should use tableView(_:cellForRowAt:) method
// later in code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ListCell") as! ListCell
cell.inject(presenter: cellPresenter)
if let item = presenter.item(row: indexPath.row) {
cell.configure(item: item)
}
return cell
}
I am working on incorporating a chat feature in my app and everything is working smoothly using JSQMessagesViewController except that I can't get the navigation bar to show in the view controller. I have tried almost everything it seems like but it seems to be hidden by the view or something although I am not sure of this. Here is my code for the Conversation view controller:
import UIKit
import JSQMessagesViewController
import Firebase
import AVKit
class ConvoVC: JSQMessagesViewController {
var chatRoomId = Variables.chatRoomID
var outgoingBubbleImageView: JSQMessagesBubbleImage!
var incomingBubbleImageView: JSQMessagesBubbleImage!
var messages = [JSQMessage]()
override func viewDidLoad() {
super.viewDidLoad()
senderId = uid!
senderDisplayName = uid!
let factory = JSQMessagesBubbleImageFactory()
incomingBubbleImageView = factory?.incomingMessagesBubbleImage(with: UIColor.jsq_messageBubbleLightGray())
outgoingBubbleImageView = factory?.outgoingMessagesBubbleImage(with: UIColor.jsq_messageBubbleBlue())
collectionView.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero
let query = ref.child("ChatRooms").child(chatRoomId!).child("Messages").queryLimited(toLast: 10)
query.observe(.childAdded, with: { (snapshot) in
if snapshot.exists(){
if let data = snapshot.value as? [String: String],
let id = data["sender_id"],
let name = data["name"],
let text = data["text"],
!text.isEmpty
{
if let message = JSQMessage(senderId: id, displayName: name, text: text)
{
self.messages.append(message)
self.finishReceivingMessage()
}
}
}
})
//let navigationBar = UINavigationBar(frame: CGRect(0, 0, self.view.frame.size.width, 64)) // Offset by 20 pixels vertically to take the status bar into account
//navigationBar.backgroundColor = UIColor.white
//navigationBar.delegate = self as! UINavigationBarDelegate
// Create a navigation item with a title
//let navigationItem = UINavigationItem()
//navigationItem.title = contacts[i].firstName
// Create right button for navigation item
setupBackButton()
// Make the navigation bar a subview of the current view controller
//self.view.addSubview(navigationBar)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
self.navigationController?.isNavigationBarHidden = false
}
func setupBackButton() {
let backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.plain, target: self, action: #selector(backButtonTapped))
navigationItem.leftBarButtonItem = backButton
}
#objc func backButtonTapped() {
dismiss(animated: true, completion: nil)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
if message.senderId == senderId {
cell.textView!.textColor = UIColor.white
} else {
cell.textView!.textColor = UIColor.black
}
return cell
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
let messageRef = ref.child("ChatRooms").child(chatRoomId!).child("Messages").childByAutoId()
let message = ["sender_id": senderId, "name": senderDisplayName, "text": text]
messageRef.setValue(message)
finishSendingMessage()
JSQSystemSoundPlayer.jsq_playMessageSentSound()
self.finishSendingMessage()
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item]
if message.senderId == senderId {
return outgoingBubbleImageView
}else {
return incomingBubbleImageView
}
}
}
Here is the code for the didSelectRow method in the class that comes before the conversation view controller:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let chatFunctions = ChatFunctions()
ref.child("users").child(uid!).observeSingleEvent(of: .value) { (snapshot) in
let userOne = User(snapshot: snapshot)
self.chatRoomIdentity = chatFunctions.startChat(user1: userOne , user2: self.matches[indexPath.row])
self.senderDisName = self.matches[indexPath.row].firstName
Variables.chatRoomID = self.chatRoomIdentity
self.present(ConvoVC(), animated: true, completion: nil)
}
}
Thanks in advance!
You are presenting it modally on this line which would present it full screen (so no navigation bar):
self.present(ConvoVC(), animated: true, completion: nil)
Push it onto the navigation stack instead like this:
self.navigationController?.pushViewController(ConvoVC(), animated: true)
If you don't have your first controller set as the root view controller of a navigation controller, make sure to do so beforehand.
Either that or set ConvoVC as a root view controller of a navigation controller and present that navigation controller
self.present(navigationController, animated: true, completion: nil).
The first solution will let you pop the ConvoVC and back to the previous view controller via the navigation bar, whereas the latter will not (you would have to dismiss it).
I highly recommend reading up on iOS navigation & segues as it's a core aspect of any iOS application.