Can I pass an image on the SecondViewController to the FirstViewController? - swift

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
}
}

Related

Protocol Function not being called

Please assist. What I'm trying to achieve
is that when I tap on a specific collectionViewCell the ReportDetailsMapController is pushed and my reports[indexPath.item] is sent from MainController to the ReportDetailsMapController.
PROTOCOL:
protocol MainControllerDelegate: class {
func sendReport(data: ReportModel)
}
FIRST VC:
class MainController: UIViewController, UICollectionViewDelegate,UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var delegate: MainControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let report = reports[indexPath.item]
//When I print(report.name) here. Everything executes correctly
self.delegate?.sendReport(data: report)
self.navigationController?.pushViewController(ReportDetailsMapController(), animated: true)
}
}
SecondVC:
class ReportDetailsMapController: UIViewController, MainControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let mc = MainController()
mc.delegate = self
}
func sendReport(data: ReportModel) {
print(data.name)//This does not execute when ReportDetailsMapController is loaded.
print("Report sent")
}
}
ReportModel:
class ReportModel: NSObject {
var name: String?
var surname: String?
var cellNumber: String?
}
That method is not called because you didn't assign the view controller you're pushing to the delegate property.
When the cell is selected, you could do in this order: initialize the view controller and assign it to the delegate, then call the delegate method and push that view controller:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let report = reports[indexPath.item]
//When I print(report.name) here. Everything executes correctly
let viewController = ReportDetailsMapController()
self.delegate = viewController
self.delegate?.sendReport(data: report)
self.navigationController?.pushViewController(viewController, animated: true)
}
However, I think a simpler and more elegant way would be to simply create that property on the ReportDetailsMapController and inject it before pushing it.
There's a similar question/answer related to that here: passing data from tableview to viewContoller in swift

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

How can I go back to the previous ViewController from UICollectionViewCell?

There are two pages(view controllers) in Main.storyboard: FirstViewController and SecondViewController. I have added a button into FirstViewController. Here is the button click scope:
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MainPage") as! SecondViewController
controller.modalPresentationStyle = .fullScreen
show(controller, sender: nil)
I have added UICollectionView into SecondViewController. UICollectionViewCell is also added (with .xib file) I have added back button in to .xib file. And I wanted to write below code for back button action(please focus func btnBack)
import UIKit
class ViewCell: UICollectionViewCell {
#IBOutlet weak var lblOutlet: UILabel!
#IBOutlet weak var contentViewOutlet: UIView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func btnBack(_ sender: UIButton) {
present(controller, animated: true, completion: nil)
}
}
but as you known, ViewCell has no member dismiss. What i am trying to do is going back to the FirstViewController with this back button in UICollectionViewCell. And yes, app user see this back button in each ui collection records.
May be you can use protocol & delegate pattern to pop back from second view controller. You can add in your CollectionViewCell file a protocol like
protocol popBackDelegate {
func popBack()
}
then inject it in your CollectionViewCell
var popBackDelegate: popBackDelegate!
After that you can call it in your btnBack action
popBackDelegate.popBack()
Now you have to bind it your second view controller's collection view's cellforitem event
cell.popBackDelegate = self
Lastly you can conform this protocol func in your view controller and pop back in that.
Hopefully I helped you with that.
I would suggest add a callback in ViewCell for such small interaction as below,
class ViewCell: UICollectionViewCell {
#IBOutlet weak var lblOutlet: UILabel!
#IBOutlet weak var contentViewOutlet: UIView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
public var dismiss: (()-> Void)? = nil
#IBAction func btnBack(_ sender: UIButton) {
self.dismiss?()
}
}
Now, in your ViewController bind that callback inside cellForItemAt as,
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: ViewCell = // Your code to dequeue cell
cell.dismiss = { [weak self] in
self?.dismiss(animated: true, completion: nil) // OR Pop whatever
}
}

How do you Tap to hide keyboard first, then tap again to select cell in Swift?

I have the following extension, which hides the keyboard when ever a tap is registered anywhere in the view.
//Extension to hide the keyboard when tap anywhere
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
This used in the viewDidLoad() of my ViewController which also is a delegate/datasource to a TableView Controller.
self.hideKeyboardWhenTappedAround()
The dismissal of the keyboard works perfectly well, although the behaviour that I am after is for the keyboard to be dismissed first at first tap anywhere on the view/tableview before the user taps again to select a row from the search results in a tableview.
Currently a tap anywhere is not only dismissing the keyboard but also selecting the cell where the user has tapped.
Add notification observer and Check for keyboard in didselectrow method.
var isKeyBoard = false
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillDisappear(_:)), name: Notification.Name.UIKeyboardWillHide, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillAppear(_:)), name: Notification.Name.UIKeyboardWillShow, object: nil)
}
#objc func keyboardWillAppear(_ notification: NSNotification) {
if let userInfo = notification.userInfo,
isKeyBoard = true
}
}
#objc func keyboardWillDisappear(_ notification: NSNotification) {
isKeyBoard = false
}
deinit {
NotificationCenter.default.removeObserver(self)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if isKeyBoard {
self.view.endEditing(true)
return
}
}
Just a Normal check you can initialise as
Keyboard is Open That means A TextField Is acting as a First Responder
Now you just simply need to check in didSelect Method here I am Showing you in my collectionView DidSelect Method same as you can do in your Tableview DidSelect
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
/// Check do TF is First Responder
/// If yes Just Hide the Keyboard for first time
if propertySearchTF.isFirstResponder {
self.view.endEditing(true)
return
}
/// if TF is not First Responder
/// Execution will start from here
/// DidSelect Code
}
Tested in TableView :
TF Outlet
#IBOutlet weak var myTF: UITextField!{
didSet{
myTF.becomeFirstResponder()
}
}
TableView Method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if self.myTF.isFirstResponder {
self.view.endEditing(true)
return
}
print(selectedKey)
selectedimage = images[indexPath.row] as UIImage
performSegue(withIdentifier: "segue", sender: self)
}
You have to add the gesture on tableView with cancelsTouchesInView = true
extension UITableView {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
tap.cancelsTouchesInView = true
self.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
self.endEditing(true)
}
}
Call from viewDidLoad() tableView.hideKeyboardWhenTappedAround()

How to tell if button in collection view header is selected when populating collection view?

How do I tell if I button in the collection view header is selected when populating my collection view? I have 2 tabs in the header which determine which data I populate the collection view with so I want to be able to switch the data and reload the collection view when the user selects a different tab.
some code from header class as requested...I don't see the point though it's just a button. I want to see if this button is selected while populating the cells and cell count etc.
class UserSearchHeader: UICollectionReusableView {
#IBOutlet weak var friendsButton: UIButton!
#IBOutlet weak var peopleButton: UIButton!
#IBOutlet weak var customSlider: UIView!
override func awakeFromNib() {
super.awakeFromNib()
self.friendsButton.selected = true
customSlider.layer.cornerRadius = customSlider.frame.height / 2
}
#IBAction func friendsButtonTapped(sender: AnyObject) {
if self.friendsButton.selected == false {
self.friendsButton.selected = true
self.peopleButton.selected = false
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.customSlider.frame.origin.x = 0
})
}
}
UICollectionviewHeader + UI Button
UIcollectionviewHeader are using dequeueReusableSupplementaryView and for some reason it created a UIView Infront of every Headerview, this would block all gesture that are happening. the solution is to bring the view to front like so:
override func awakeFromNib() {
super.awakeFromNib()
self.bringSubview(toFront: friendsButton) // this depends on you .xib
}
this will solve your gesture issue.
Theres Multiple way to Create a UIButton Action inside a CollectionViewHeader.
AddTarget (as Answered by #derdida)
Drag and Drop Action In CollectionViewHeader Class.
Add Target :-(refer to derdida answer)
inside of viewForSupplementaryElementOfKind// ps: you need to register your CollectionView Class & Nib
Add Target to UIButton like so.
header.friendsButton.tag = 0
header.friendsButton.addTarget(self, action: #selector(self.HeaderClick(_:)), for: .touchUpInside)
Created the Function like so.
#objc func HeaderClick1(_ sender : UIButton){ print("sender.tag \(sender.tag)") }//sender.tag 0
Drag and Drop Action In CollectionViewHeader Class.
for this example i will used #Wayne Filkins Question.
Ctrl + Drag UIButton To Header Class
Select Connection Type to Action create the function like so
#IBAction func friendsButtonTapped(_ sender: Any) { print("something from friendsButtonTapped") }
and thats it.
but the main key here is to get the view to be to front. used debug view hierarchy to know more.
Someone can fixed my code view please!
Update Solution
Using UIButtonSetOnClickListener.Swift and heres the code:
extension UIControl {
func setOnClickListener(for controlEvents: UIControl.Event = .primaryActionTriggered, action: #escaping () -> ()) {
removeTarget(nil, action: nil, for: .allEvents)
let sleeve = ClosureSleeve(attachTo: self, closure: action)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for:
controlEvents)
}
}
class ClosureSleeve {
let closure: () -> ()
init(attachTo: AnyObject, closure: #escaping () -> ()) {
self.closure = closure
objc_setAssociatedObject(attachTo, "[\(arc4random())]", self, .OBJC_ASSOCIATION_RETAIN)
}
#objc func invoke() {
closure()
}
}
Here how to used is:
Add Target :-(refer to derdida answer)
inside of viewForSupplementaryElementOfKind// ps: you need to register your CollectionView Class & Nib
header.friendsButton.setOnClickListener {
//put your code here
}
There are 2 delegate methods who are important to decide which items will be shown. For example:
You have 2 different items, they are populated in:
let items1 = [Item]()
let items2 = [Item]()
Then you have a variable, that holds which items should be shown:
let items1Shown:Bool = true
Now implement the delegate methods with something like:
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if(items1Shown == true) {
return items1.count
} else {
return items2.count
}
}
And
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var item:Item!
if(items1Shown == true) {
item = items1[indexPath.row]
} else {
item = items1[indexPath.row]
}
// format your cell
}
And implement any button function
func ChangeItems() {
if(items1Shown == true) {
items1Shown = false
} else {
items1Shown = true
}
// reload your collectionView
self.collectionView.reloadData()
}
Edit:
Why not giving your button a target? (Add this where you dequeue your headerView!)
headerView.friendsButton.addTarget(self, action: #selector(YourViewControllerWithFunction.cellButtonClick(_:)), forControlEvents: .TouchUpInside)
// for example:
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: headerId, forIndexPath: indexPath)
headerView.friendsButton.addTarget(self, action: #selector(YourViewControllerWithFunction.cellButtonClick(_:)), forControlEvents: .TouchUpInside)
return headerView
}
// Class YourViewControllerWithFunction
func cellButtonClick(button: UIButton) {
// you can do anything with that button now
}