present UIImagePickerController from collectionViewCell class - swift

I am having a little trouble presenting the UIImagePicker from a collectionViewCell. I know collectionViewCells do not act as the picker delegate by default so I did try to call a method from the UIViewController class operating the cell holding the imageView tapped. There are no errors showing in my console, but when I run the app and try to present the UIImagePickerController nothing happens and I receive the "Warning: Attempt to present on whose view is not in the window hierarchy!". I would like to present the UIImagePickerController when user taps on the profileImageView. Thanks in advance for help!
// LoginCell class holding the imageView to call the UIImagePickerController method from LoginController class
lazy var profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.addGestureRecognizer(UITapGestureRecognizer(target:self, action: #selector(handleSelectProfileImage)))
imageView.isUserInteractionEnabled = true
return imageView
}()
var loginController: LoginController?
func handleSelectProfileImage() {
let loginController = LoginController()
loginController.showImagePicker(sendingVC: loginController)
}
// LoginController class as UIImagePickerController delegate
func showImagePicker(sendingVC: LoginController) {
let picker = UIImagePickerController()
picker.delegate = self
picker.allowsEditing = true
present(picker, animated: true, completion: nil)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let loginCell = collectionView.dequeueReusableCell(withReuseIdentifier: loginCellId, for: indexPath) as! LoginCell
loginCell.delegate = self
return loginCell
}

try this, in your cell:
func handleSelectProfileImage() {
guard let loginController = delegate as? LoginViewController else {
return
}
loginController.showImagePicker()
}
in your LoginController
func showImagePicker() {
let picker = UIImagePickerController()
picker.delegate = self
picker.allowsEditing = true
present(picker, animated: true, completion: nil)
}

Related

How do I set an image in a cell class from a ImagePicker Controller

I'm trying to change the image of a button to an image that the user picked in the imagePicker, but I am presenting the image picker in the ViewController class and the button is in the cell class.
I'm not sure how to set it from a different class. Here is the code I have for presenting the image picker and the way the button is created:
Here is my profileVC class:
class UserProfileVC: UICollectionViewController, UICollectionViewDelegateFlowLayout, UserProfileHeaderDelegate {
func handleEditBannerTapped(for header: ProfileHeader) {
let imagePicker = UIImagePickerController()
imagePicker.allowsEditing = true
present(imagePicker, animated: true, completion: nil)
}
}
The above code is working fine and is presenting all pictures on the device for the user to choose.
Here is my ProfileHeader Cell class
class ProfileHeader: UICollectionViewCell, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
lazy var profileBanner: UIButton = {
let button = UIButton()
button.clipsToBounds = true
let imageTapped = UITapGestureRecognizer(target: self, action: #selector(handleBannerTapped))
imageTapped.numberOfTapsRequired = 1
button.isUserInteractionEnabled = true
button.addGestureRecognizer(imageTapped)
return button
}()
#objc func handleBannerTapped() {
delegate?.handleEditBannerTapped(for: self)
}
}
How do I set the image that the user picks to the profileBanner Button from the UserProfileVC class?
I'm assuming UserProfileVC is the main class, which contains a collection view, and ProfileHeader is a cell inside that collection view? You need to know the IndexPath of the cell, then do:
if let cell = collectionView.cellForItem(at: indexPath) as? ProfileHeader {
cell.profileBanner.setImage(imageThatYouWant, for: .normal)
}

navigationController?.pushViewController is not working

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?()
}
}

Popover attached to cell in UICollectionView moving when is called the ReloadData of this collection view

I have a UICollectionView that the user can clicks and it shows a popover attached to the cell clicked. In iOS 12, the popover stayed in the same origin (x,y or cell) if the reload data was called or not, but in iOS 13, the popover moves between the cells when the reload data of the collection view is called.
Follow a video of the behavior in iOS 13:
https://vimeo.com/385021234
The presentation is done using the following code:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) else {
return
}
presentPopover(from: self, cell: cell)
}
func presentPopover(from view: UIViewController, cell: UIView) {
let popoverView = PopoverViewController(nibName: "PopoverViewController", bundle: nil)
let popover: UIPopoverPresentationController = popoverView.popoverPresentationController!
popover.sourceRect = cell.bounds
popover.sourceView = cell
view.present(popoverView, animated: true, completion: nil)
}
And the PopoverViewController is using the modalPresentationStyle = .popover
Anyone had this issue before in iOS 13?
Our application was working fine in iOS 11 and 12.
I have an example of this behavior in the following git repository: https://github.com/diegodossantos95/UICollectionViewPopoverBug
Steps to reproduce in the repository:
Click on a collection cell
Popover opens
Wait for the reload data
Thanks,
Diego.
Instead of attaching the popover to a cell who you can guarantee won't be dequeued, you could use a temporary UIView created from the cell's frame and then attach the popover to that. Here's the relevant code to accomplish this.
class ViewController: UIViewController {
var timer = Timer()
var popOverTempView = UIView()
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(reloadData), userInfo: nil, repeats: true)
self.collectionView.addSubview(popOverTempView)
popOverTempView.isHidden = true
popOverTempView.backgroundColor = .clear
}
}
extension ViewController: UICollectionViewDelegate {
func presentPopover(from view: UIViewController, cell: UIView) {
let popoverView = PopoverViewController(nibName: "PopoverViewController", bundle: nil)
let popover: UIPopoverPresentationController = popoverView.popoverPresentationController!
popoverView.delegate = self
popOverTempView.frame = cell.frame
popOverTempView.isHidden = false
popover.sourceRect = popOverTempView.bounds
popover.sourceView = popOverTempView
view.present(popoverView, animated: true, completion: nil)
}
}
extension ViewController: PopoverViewControllerDelegate {
func willDismissPopover() {
popOverTempView.isHidden = true
}
}
protocol PopoverViewControllerDelegate {
func willDismissPopover()
}
class PopoverViewController: UIViewController {
var delegate: PopoverViewControllerDelegate?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
delegate?.willDismissPopover()
}
}
Update 5/20/20:
I think I found a cleaner more direct fix. Instead of using a cell's bounds directly, convert it's contentView's frame to the collectionView's coordinate space, then use that as the sourceRect and present from the current view controller's view.
func presentPopover(from view: UIViewController, cell: UICollectionViewCell, indexPath: IndexPath) {
let popoverView = PopoverViewController(nibName: "PopoverViewController", bundle: nil)
let popover: UIPopoverPresentationController = popoverView.popoverPresentationController!
var rect = cell.convert(cell.contentView.frame, to: collectionView)
rect = collectionView.convert(rect, to: self.view)
popover.sourceRect = rect
popover.sourceView = self.view
view.present(popoverView, animated: true, completion: nil)
}
When you call reloadData this causes the collection view to discard any currently visible items (including placeholders) and recreate items based on the current state of the data source object.
Se the difference between the cells frame before and after the reload data:
- some : <UICollectionViewCell: 0x7fd8abd02190; frame = (73.5 10; 50 50); clipsToBounds = YES; hidden = YES; opaque = NO; layer = <CALayer: 0x600001b50e20>>
- some : <UICollectionViewCell: 0x7fd8abd02190; frame = (519.5 10; 50 50); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x600001b50e20>>
if you need to change only one or more itens item on the collection try to use:
func reloadItems(at: [IndexPath])

popoverController not seguing

Hi I have a popover view that is showing an array. I am wondering if there is a way that I could somehow segue back which item's in the array are selected.
#IBAction func popOverButton(_ sender: UIButton)
{
let controller = TableViewController()
//This is just a regular tableViewController nothing special
controller.modalPresentationStyle = .popover
// configure the Popover presentation controller
let popController: UIPopoverPresentationController? = controller.popoverPresentationController
popController?.permittedArrowDirections = [.down]
popController?.delegate = self
popController?.sourceView = sender
popController?.sourceRect = sender.bounds
popController?.backgroundColor = .white
self.parent?.present(controller, animated: true, completion: { })
}
Here is what it looks like
Any help is appreciated thanks
Easiest way is to create a delegate and when a cell is selected pass the selection back to the presenting view controller. Then setting up the UITableViewDelegate method didSelectRowAtIndexPath to call the delegate method. Something like this:
#protocol PopoverOptionSelectionDelegate {
func itemSelected(item:String);
}
Implement the method in your presenting VC
class PresnetingViewController, PopoverOptionSelectionDelegate {
#IBAction func popOverButton(_ sender: UIButton) {
let controller = TableViewController()
controller.delegate = self //----Important---//
//This is just a regular tableViewController nothing special
controller.modalPresentationStyle = .popover
// configure the Popover presentation controller
let popController: UIPopoverPresentationController? =
controller.popoverPresentationController
popController?.permittedArrowDirections = [.down]
popController?.delegate = self
popController?.sourceView = sender
popController?.sourceRect = sender.bounds
popController?.backgroundColor = .white
self.parent?.present(controller, animated: true, completion: { })
}
func itemSelected(item:String) {
//DISMISS YOUR POPOVER MAYBE AND DO SOMETHING WITH "ITEM" HERE
}
}
class TableViewController,UITableViewDelegate {
weak var delegate:PopoverOptionSelectionDelegate?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
delegate?.itemSelected(self.itemsArray[indexPath.row])
}
}

delegate pass to other ViewController

I want to pass a delegate like var delegate: Diary?from my HeaterViewController to my DetailViewController to get the core data attributes of it. So I have set the delegate in func didselectItem view.delegate = diarysbut it don´t run I have no segue to pass the data between the view controllers. So the question was how I can pass the delegate between the view controllers to get the title from the core data entry.
The other thing is I want to get access to the func from the headerviewcontroller from a other viewcontroller. I have tried with
let vc = storyboard!.instantiateViewController(withIdentifier: "DiaryController") as! DiaryController
vc.loadDiarys()
but it don´t run so I need the func from the DiaryController (loadDiarys) in this viewcontroller. I have no segue to use.
Code from DetailViewController:
var delegate: Diary?
AddTitle.text = delegate.title
Code from HeaderViewController:
var diary = [Diary] () {
didSet {
self.DiaryCollectionView.reloadData()
}
}
var mgdContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
load new Entry:
func loadDiarys () {
let loadRequest: NSFetchRequest<Diary> = Diary.fetchRequest()
diary = try! mgdContext.fetch(loadRequest as! NSFetchRequest<NSFetchRequestResult>) as! [Diary]
print("loadD")
}
hand over delegate to other ViewController (DetailDiaryController)
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let diarys = diary[indexPath.row]
let view = DetailDiaryController.instantiateFromNib()
view.delegate = diarys
let window = UIApplication.shared.delegate?.window!
let modal = PathDynamicModalPan()
modal.showMagnitude = 200.0
modal.closeMagnitude = 130.0
view.closeButtonHandler = {[weak modal] in
modal?.closeWithLeansRandom()
return
}
view.bottomButtonHandler = {[weak modal] in
modal?.closeWithLeansRandom()
return
}
modal.show(modalView: view, inView: window!)
}
}