Swift Popover Anchor - ios

I'm trying to show a popover over an image and I'm failing trying to point the anchor point to my image border (the image is the info icon).
Here is my code:
The button action:
#IBAction func infoTapped(_ sender: AnyObject) {
self.performSegue(withIdentifier: "InfoPopOver", sender: nil)
}
And the prepare for segue:
extension AddExpenseViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "InfoPopOver" {
if let vctr = segue.destination as? MyPopOverViewController {
vctr.modalPresentationStyle = .popover
vctr.popoverPresentationController?.delegate = self
vctr.popoverPresentationController?.sourceView = self.view
vctr.popoverPresentationController?.sourceRect = infoIcon.frame
}
}
}
}
class MyPopOverViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = CGSize(width: 185, height: 80)
}
}
Here a couple of screenshots:
Main View
Popover shown
The best I got was setting the coordinates manually, but it doesn't fit neither every screen type...

I would change it to this:
vctr.popoverPresentationController?.sourceView = infoIcon.superview
vctr.popoverPresentationController?.sourceRect = infoIcon.frame
That way you can be sure that you are using the superview of the infoIcon and the frame will be in the correct position.

Related

Segue and delegates in UIKit

I have 2 Controllers: MainVC and SideMenuVC.
I wanted to modify MainVC using SideMenuVC, so created delegate of SideMenuVC ( as well, there's Emdebed segue "name..." to "Side Menu View Controller" on storyboard because MainViewController has subView, which contains ContainerView - this container is our SideMenuVC. And this delegate works as he should.
However, due to logic in the app, I also need to send data from MAINVC to SIDEMENUVC.
So i did the same - created another delegate of second VC... But I turned out, MainViewControllerDelegate is not responding in SideMenuViewController. And i'm absolutely clueless...
Yes, i do implement necessary protocols in both classes, in extension!
Code of both VCs below, screens of storyboard in the attachment
MainViewController + MainViewControllerDelegate
protocol MainViewControllerDelegate{
func isImageLoaded(_ isLoaded:Bool)
}
class MainViewController: UIViewController {
/* ... */
var delegate: MainViewControllerDelegate?
var sideMenuViewController: SideMenuViewController?
private var isSideMenuPresented:Bool = false
private var isImageLoaded:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
self.isImageLoaded = false
self.setupUI()
self.delegate?.isImageLoaded(false)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "MainVC_SideMenuVC_Segue")
{
if let controller = segue.destination as? SideMenuViewController
{
self.sideMenuViewController = controller
self.sideMenuViewController?.delegate = self
}
}
}
/* ... */
//I'm using PHPicker, and when new image is selected, i want to send "true" via delegate
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
if let itemProvider = results.first?.itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self){
let previousImage = self.presentedImage.image
itemProvider.loadObject(ofClass: UIImage.self){ [weak self] image, error in
DispatchQueue.main.async {
guard let self = self, let image = image as? UIImage, self.presentedImage.image == previousImage else {
return
}
self.presentedImage.image = image
self.isImageLoaded = true;
self.delegate?.isImageLoaded(true)
}
}
}
}
SideMenuViewController + SideMenuViewControllerDelegate
protocol SideMenuViewControllerDelegate{
func hideSideMenu()
func performAction(_ type:OperationType)
}
class SideMenuViewController: UIViewController {
/*...*/
var delegate: SideMenuViewControllerDelegate?
var mainViewController: MainViewController?
private var menuData: [ExpandingCellModel] = []
private var isImageLoaded: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// self.mainViewController?.delegate = self
menuData = setupData()
setupUI()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = segue.destination as? MainViewController {
controller.delegate = self
}
}
}
/* ... */
Here is what I think is happening.
There is segue happening from MainVC to SideMenuVC but there is no segue actually happening between SideMenuVC to MainVC in my opinion.
Happening is keyword because there is an EmbedSegue from MainVC to SideMenuVC but where is the segue from SideMenuVC to MainVC ? You did some connection in storyboard but nothing is happening in my opinion.
That is why in override func prepare is being called as planned in MainViewControllerDelegate and the delegate is getting set but it is not getting set in SideMenuViewController since override func prepare doesn't get called as no segue happens.
What you can do instead which might work is set both the delegates inside prepare in MainViewControllerDelegate
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "MainVC_SideMenuVC_Segue")
{
if let sideMenuVC = segue.destination as? SideMenuViewController
{
sideMenuVC.delegate = self
// assign MainViewControllerDelegate here
self.delegate = sideMenuVC
}
}
}
Check now if data is sent back to main view controller also.
If your issue is still not yet solved, please have a look and try this small example I set for you in github to show passing data between mainVC and embeddedVC and it might give you some hints.
You can see the result of this in action here: https://youtu.be/J7C7SEC04_E

swift4: can't pass value to previous view controller from current popover

I'm using swift4, i present view controller modally as popover and want to pass data back to previous view controller when dismissing current popover, this is the first View controller :
import UIKit
class SearchResultViewController: UIViewController, UIPopoverPresentationControllerDelegate, PopoupDelegate {
#IBOutlet var errorLable: UILabel!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "pop") {
let dest = segue.destination
if let pop = dest.popoverPresentationController {
dest.preferredContentSize = CGSize(width: self.view.frame.size.height - 20, height: 500)
pop.delegate = self
}
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
}
and this the popover code:
import UIKit
class SearchPopoverViewController: UIViewController {
#IBOutlet var cityLable: UILabel!
var delegate: PopoupDelegate?
#IBAction func closeButtonAction(_ sender: Any) {
self.delegate?.popupValueSelected(value: "hiiiiii from the other side")
self.dismiss(animated: false, completion: nil)
}
}
and this is the PopoupDelegate
import Foundation
protocol PopoupDelegate {
func popupValueSelected(value: String)
}
when i click in the popover close button, the popover should dismiss and the errorLable in SearchResultViewController should change it's text to new passed text, but none happens, only the popover dismiss,
what should i do?
You delegate function is wrong, function inside function in SearchResultViewController:
func popupValueSelected(value: String) {
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
}
It should be:
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
After editing the question :
You never assigned any instance to the delegate variable. Try to change delegate variable name to popoUpDelegate.
pop.delegate is for UIPopoverPresentationControllerDelegate not your delegate. This function should be like this :
if (segue.identifier == "pop") {
if let dest = segue.destination.popoverPresentationController as? SearchPopoverViewController {
dest.preferredContentSize = CGSize(width: self.view.frame.size.height - 20, height: 500)
dest.popoUpDelegate = self
}
}
}
And your SearchPopoverViewController look like this :
class SearchPopoverViewController: UIViewController {
#IBOutlet var cityLable: UILabel!
var popoUpDelegate: PopoupDelegate?
#IBAction func closeButtonAction(_ sender: Any) {
self.popoUpDelegate?.popupValueSelected(value: "hiiiiii from the other side")
self.dismiss(animated: false, completion: nil)
}
}
you have to define delegate to SearchResultViewController.
var PopoupDelegate : PopoupDelegate?
and before pop viewcontroller assign delegate to popoupDelegate
PopoupDelegate.delegate = self
and you have right function inside function. first make it correct.
Try this. Hope this will work. and let me know in case of any concern
When you are presenting SearchPopoverViewController from SearchResultViewController you need to set delegate.
Using segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "segueToPopOver") {
let dest = segue.destination
if let vc = dest.popoverPresentationController as? SearchPopoverViewController {
//set other properties
vc.delegate = self
}
}
}
Using UINavigationController
let vc = SearchPopoverViewController() // initialise view controller
vc.delegate = self
present(vc, animated: true, completion: nil)
Respond to delegate
#IBAction func closeButtonAction(_ sender: Any) {
self.delegate?.popupValueSelected(value: "hiiiiii from the other side")
self.dismiss(animated: false, completion: nil)
}
In SearchResultViewController implement delegate method
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
I think you have a fundamental misunderstanding as to how segues and protocols work for view controllers. Frankly, I would recommended reviewing the topic in Apple's App Development with Swift. This is written clearly with exercises to help you understand the basics of passing information between view controllers. I have been where you are, and while the suggestions and comments have been correct, you don't seem to have the foundation to understand what they are saying. Good luck!
I used popover dismiss delegate instead of protocol:
this code solved my problem:
in the SearchResultViewController:
func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) {
if let searchPopoverViewController =
popoverPresentationController.presentedViewController as? SearchPopoverViewController{
self.passed_city_id = searchPopoverViewController.selected_city_id
}
}
in the SearchPopoverViewController
#IBAction func searchButtonAction(_ sender: Any) {
self.selected_city_id = 10
popoverPresentationController?.delegate?.popoverPresentationControllerDidDismissPopover?(popoverPresentationController!)
self.dismiss(animated: true, completion: nil)
}
you can use "Unwind segue". pls refer
https://medium.com/#mimicatcodes/create-unwind-segues-in-swift-3-8793f7d23c6f

Can't get my Delegate protocol to work when dealing with a Container VC?

I cannot get my delegate protocol to work. I used this stack overflow questions as a guide dispatch event to parent ViewController in swift . I don't know if things have changed in Swift 3 since this post, but my function in my parentViewController is never getting called. Here is my setup.
//PROTOCOL
protocol PDPPropDetailsDelegate {
func buttonPressed(PropDetailsVC: propertyDetailsVC)
}
// CHILD VIEW CONTROLLER
class propertyDetailsVC: UIViewController {
var delegate: PDPPropDetailsDelegate?
#IBAction func emailButton(_ sender: AnyObject) {
self.delegate?.buttonPressed(PropDetailsVC: self)
}
}
The Button is getting called in Child View Controller.
// PARENT VIEW CONTROLLER
class ImageDetailsVC: UIViewController, PDPPropDetailsDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "container"{
container = segue.destination as! ContainerViewController
}
}
#IBAction func segmentControlAct(_ sender: Any) {
switch segmentControllerView.selectedIndex {
case 0: print("case 1")
container!.segueIdentifierReceivedFromParent("first")
case 1: print("case 2")
container!.segueIdentifierReceivedFromParent("second")
PropertyDetailsVC.delegate = self // **WHERE I SET DELEGATE**
setUpPropertyDetailsUI(property: filterImages)
default: print("default")
}
}
func buttonPressed(PropDetailsVC: propertyDetailsVC) {
print("BUTTON PRESSED")
}
}
Button Pressed is never called. I assume it has to do with the delegate not getting set properly. Not exactly sure why that would be the case though. My setUpPropertyDetailsUI(property: filterImages) takes the Outlets from that VC and sets that works just fine. I did a breakpoint and it is called when I segment over to the PropertyDetailsVC. Any advice or suggestions?
import UIKit
open class ContainerViewController: UIViewController {
//Manipulating container views
fileprivate weak var viewController : UIViewController!
//Keeping track of containerViews
fileprivate var containerViewObjects = Dictionary<String,UIViewController>()
/** Specifies which ever container view is on the front */
open var currentViewController : UIViewController{
get {
return self.viewController
}
}
fileprivate var segueIdentifier : String!
/*Identifier For First Container SubView*/
#IBInspectable internal var firstLinkedSubView : String!
override open func viewDidLoad() {
super.viewDidLoad()
}
open override func viewDidAppear(_ animated: Bool) {
if let identifier = firstLinkedSubView{
segueIdentifierReceivedFromParent(identifier)
}
}
override open func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func segueIdentifierReceivedFromParent(_ identifier: String){
self.segueIdentifier = identifier
self.performSegue(withIdentifier: self.segueIdentifier, sender: nil)
}
override open func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == segueIdentifier{
//Remove Container View
if viewController != nil{
viewController.view.removeFromSuperview()
viewController = nil
}
//Add to dictionary if isn't already there
if ((self.containerViewObjects[self.segueIdentifier] == nil)){
viewController = segue.destination
self.containerViewObjects[self.segueIdentifier] = viewController
}else{
for (key, value) in self.containerViewObjects{
if key == self.segueIdentifier{
viewController = value
}
}
}
self.addChildViewController(viewController)
viewController.view.frame = CGRect(x: 0,y: 0, width: self.view.frame.width,height: self.view.frame.height)
self.view.addSubview(viewController.view)
viewController.didMove(toParentViewController: self)
}
}
}
import UIKit
class EmptySegue: UIStoryboardSegue{
override func perform() {
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
You seem to be a little confused at the flow of the app. Here's an answer I wrote for someone else's question about this same topic:
https://stackoverflow.com/a/45312362/3832646
Your protocol and Child view controller look great, but there are quite a few things amiss with the rest of your code here:
your prepare(for segue:_, sender:_) is typically where you would set the delegate for the destination (child) view controller.
PropertyDetailsVC.delegate = self won't do anything - you need an instance of the view controller to set its delegate.
It looks like you're using some sort of container global variable that I'm not sure what it would be for.
Take a look at the answer I posted and have another go. It's in Swift 3.

Popover on iPhone (Swift 3) from Programmatically Created Button

I am trying to have a popover appear on an iPhone when I click on a programmatically created button. However, I am instead finding that the popover is taking over the entire screen. I've coded the following thus far in the popover view controller's class:
import UIKit
class GroupSetsPopoverController: UIViewController, UIPopoverPresentationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = CGSize(width: 375, height: 162)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "infoButtonSegue" {
let popoverViewController = segue.destination as! GroupSetsPopoverController
let pvc = popoverViewController.popoverPresentationController
pvc?.delegate = self
//popoverViewController.modalPresentationStyle = UIModalPresentationStyle.popover
//popoverViewController.popoverPresentationController!.delegate = self
}
}
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}
Any guidance would be appreciated. I've tried to follow various tutorials and posts here on Stack Overflow, to no avail.
You can check out the answer and library for your references. If the GroupSetsPopoverController is your popover viewcontroller, you can do something like this:
init(for sender: UIView)) {
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .popover
guard let pop = popoverPresentationController else { return }
pop.sourceView = sender
pop.sourceRect = sender.bounds
pop.delegate = self
}

How to call a function after performing a segue in swift?

Hello I want to know what is the best way to call a func after performing a segue on swift. I want to click on a button and perform a segue and start my viewDidLoad() with a new func.
//FIRST VIEW CONTROLLER
import UIKit
class CelebritiesViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
questionsLevel1()
pickingRandomQuestion()
hide()
finishGame.hidden = true
nextLevel.hidden = true
if isButtonClick == true {
questionsLevel2()
}
}
}
//SECOND VIEW CONTROLLER
import UIKit
class Level2Section: UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "level2" {
if let destinationLevel2 = segue.destinationViewController as? CelebritiesViewController {
destinationLevel2.isButtonClick = true
}
}
}
}
PS: After finish first section of questions user can choose to finish the game or go to next level, next level is a new view controller so we performing this segue in a second view controller not in the same one.
ViewController1:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SegueIdentifierHere" {
if let destVC = segue.destinationViewController as? ViewController2 {
destVC.isButtonClick = true
}
}
}
ViewController2:
Define a Bool:
var isButtonClick:Bool!
ViewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
questionsLevel1()//This is my first section of questions
pickingRandomQuestion()//My function to make a random number of questions
if isButtonClick == true {
isButtonClick = !isButtonClick
callSecondFunction()
}
}

Resources