Problem with delegates removing annotation - swift

I have two screens. The first one (firstViewController) has a mapView with a UITapGestureRecognizer. When the user taps the screen, an annotations is added to the map and the second screen (secondViewController) is presented.
When the user dismisses the secondViewController and comes back to the first one, the annotation should be removed. I know I have to use delegation, but I just can't make it to work.
This is the code I have now:
class firstViewController: UIViewController, AnnotationDelegate {
let mapView = MKMapView()
var temporaryPinArray = [MKPointAnnotation]()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(mapView
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
mapView.addGestureRecognizer(gesture)
secondVC.annotationDelegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
mapView.frame = view.bounds
}
#objc func handleTap(_ gestureReconizer: UILongPressGestureRecognizer) {
let location = gestureReconizer.location(in: mapView)
let coordinates = mapView.convert(location, toCoordinateFrom: mapView)
mapView.removeAnnotations(mapView.annotations)
let pin = MKPointAnnotation()
pin.coordinate = coordinates
temporaryPinArray.removeAll()
temporaryPinArray.append(pin)
mapView.addAnnotations(temporaryPinArray)
// Present secondViewController
let secondVC = SecondViewController()
panel.set(contentViewController: secondVC)
panel.addPanel(toParent: self)
}
func didRemoveAnnotation(annotation: MKPointAnnotation) {
mapView.removeAnnotation(annotation)
}
}
Second View Controller
protocol AnnotationDelegate {
func didRemoveAnnotation(annotation: [MKPointAnnotation])
}
class SecondViewController: UIViewController {
var annotationDelegate: AnnotationDelegate!
let mainVC = firstViewController()
let closeButton: UIButton = {
let button = UIButton()
button.backgroundColor = .grey
button.layer.cornerRadius = 15
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(closeButton)
closeButton.addTarget(self, action: #selector(dismissPanel), for: .touchUpInside)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
closeButton.frame = CGRect(x: view.frame.width-50, y: 10, width: 30, height: 30)
}
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation(annotation: mainVC.temporaryPinArray)
}
}
Thank you so much for your help!

You created a new instance of firstViewController inside SecondViewController. This instance is unrelated to the actual first one:
let mainVC = firstViewController()
This means that temporaryPinArray is different as well. So instead of passing in this unrelated array...
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation(annotation: mainVC.temporaryPinArray)
}
Just change the function to take no parameters instead:
protocol AnnotationDelegate {
func didRemoveAnnotation() /// no parameters
}
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation() /// no parameters
}
And inside firstViewController's didRemoveAnnotation, reference the actual temporaryPinArray.
func didRemoveAnnotation() {
mapView.removeAnnotations(temporaryPinArray) /// the current array
}

Related

Title Not Showing in Simulator and doesn't shift ViewController to next after button click

Title is not showing in Simulator
import UIKit
class WelcomeSpotifyViewController: UIViewController {
private let signInButton: UIButton = {
let button = UIButton()
button.backgroundColor = .white
button.setTitle("Sign In with Spotify", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
title = "Spotify"
view.backgroundColor = .systemGreen
view.addSubview(signInButton)
signInButton.addTarget(self, action: #selector(didTapSignIn), for: .touchUpInside)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
signInButton.frame = CGRect(x:20, y: view.height-50-view.safeAreaInsets.bottom, width: view.width-40, height: 50)
}
#objc func didTapSignIn() {
let vc = SpotifyAuthViewController()
vc.navigationItem.largeTitleDisplayMode = .never
navigationController?.pushViewController(vc, animated: false)
}
}
When I launch the simulator, the title does not show. And when I click the button "Sign In with Spotify," I do not transfer to the SpotifyAuthViewController.
Here is the code for the SpotifyAuthViewController
import UIKit
import WebKit
class SpotifyAuthViewController: UIViewController, WKNavigationDelegate {
private let webView: WKWebView = {
let prefs = WKWebpagePreferences()
prefs.allowsContentJavaScript = true
let config = WKWebViewConfiguration()
config.defaultWebpagePreferences = prefs
let webView = WKWebView(frame: .zero, configuration: config)
return webView
}()
override func viewDidLoad() {
super.viewDidLoad()
title = "Sign In"
view.backgroundColor = .systemBackground
webView.navigationDelegate = self
view.addSubview(webView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
webView.frame = view.bounds
}
}
After the user is registered, I want them to be directed to a standard login/register page where they create a username, register with email, and create a password.
import UIKit
class LoginViewController: UIViewController {
private let usernameEmailField: UITextField = {
return UITextField()
}()
private let passwordField: UITextField = {
let field = UITextField()
field.isSecureTextEntry = true
return field
}()
private let loginButton: UIButton = {
return UIButton()
}()
private let createAccountButton: UIButton = {
return UIButton()
}()
private let headerView: UIView = {
return UIView()
}()
override func viewDidLoad() {
super.viewDidLoad()
addSubviews()
view.backgroundColor = .systemBackground
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
handleNotAuthenticated()
//Check auth status
}
private func handleNotAuthenticated() {
if SpotifyAuthManager.shared.isSignedIn == false {
let loginVC = WelcomeSpotifyViewController()
loginVC.modalPresentationStyle = .overCurrentContext
present(loginVC, animated: false)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//assign frames
}
private func addSubviews() {
view.addSubview(usernameEmailField)
view.addSubview(createAccountButton)
view.addSubview(passwordField)
view.addSubview(loginButton)
view.addSubview(headerView)
}
#objc private func didTabLoginButton(){}
#objc private func didTapCreateAccountButton(){}
}
If the user is not connected with Spotify, then they are redirected to the WelcomeSpotifyViewController. Once they enter their username and pass, they are taken to the home page.
import UIKit
import FirebaseAuth
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
handleNotAuthenticated()
//Check auth status
}
private func handleNotAuthenticated() {
if Auth.auth().currentUser == nil {
let loginVC = LoginViewController()
loginVC.modalPresentationStyle = .fullScreen
present(loginVC, animated: false)
}
}
}
I have been tinkering with this for days now. I can't seem to figure out the issue. Any help would be really appreciated.
I think your problem is that your WelcomeSpotifyViewController don't have navigaiton controller. You should debug and check value of navigation controller

UITapGestureRecognizer not attaching action

I created a separate class for View.
I left all the functions in the Controller.
But when I add a click on the picture, it doesn't work for some reason.
import UIKit
class APOTDView: UIView {
var imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(APOTDViewController.imageTapped(_:)))
imageView.addGestureRecognizer(tap)
return imageView
}()
}
import UIKit
class APOTDViewController: UIViewController {
let av = APOTDView()
override func viewDidLoad() {
super.viewDidLoad()
// ... add subview and constraint
}
#objc func imageTapped(_ sender: UITapGestureRecognizer) {
print("good job")
}
}
What's the matter? Please help me figure it out
Your selector in the UITapGestureRecognizer is wrong. You can not call the APOTDViewController directly.
APOTDViewController.imageTapped would be a static function, which is not available.
You can use a delegate instead.
Delegate Protocol and View.
protocol APOTDViewDelegate: AnyObject {
func viewDidTapImage()
}
class APOTDView: UIView {
weak var delegate: APOTDViewDelegate?
var imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
imageView.addGestureRecognizer(tap)
return imageView
}()
#objc func imageTapped() {
delegate?.viewDidTapImage()
}
}
ViewController:
class APOTDViewController: UIViewController, APOTDViewDelegate {
let av = APOTDView()
override func viewDidLoad() {
super.viewDidLoad()
av.delegate = self
// ... add subview and constraint
}
#objc func viewDidTapImage() {
print("good job")
}
}
This will not work, because you are calling the UIViewController method directly without any class reference or object. The solution is to use protocol or clouser to get action from view to class.
class
class APOTDView: UIView {
#objc var imageViewAction: ((UITapGestureRecognizer) -> Void)? = nil
lazy var imageView: UIImageView = {
let imageView = UIImageView()
imageView.backgroundColor = .blue
imageView.translatesAutoresizingMaskIntoConstraints = true
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector((imageTapped(_:))))
imageView.addGestureRecognizer(tap)
return imageView
}()
#objc private func imageTapped(_ sender: UITapGestureRecognizer) {
self.imageViewAction?(sender)
}
}
ViewController
class APOTDViewController: UIViewController {
let av = APOTDView()
override func viewDidLoad() {
super.viewDidLoad()
av.imageViewAction = { sender in
print("good job")
}
}
}

How to know when VC2 was dismissed on VC1?

I have ViewController1 that goes to ViewModel and then to Coordinator to present ViewController2.
The problem is: I need to know when VC2 was dismissed on VC1.
What I need to do: When VC2 is dismissed, I need to reload my table from VC1.
I can not use Delegate since I cant communicate between then (because of Coordinator).
Any help please?
Adding some code: My Coordinator:
public class Coordinator: CoordinatorProtocol {
public func openVC1() {
let viewModel = ViewModel1(coordinator: self)
guard let VC1 = ViewControllerOne.instantiate(storyboard: storyboard, viewModel: viewModel) else {
return
}
navigationController?.pushViewController(VC1, animated: true)
}
public func openVC2() {
let viewModel = ViewModel2()
guard let alertPriceDeleteVC = ViewControllerTwo.instantiate(storyboard: storyboard, viewModel: viewModel) else {
return
}
let nav = UINavigationController(rootViewController: VC2)
navigationController?.present(nav, animated: true, completion: nil)
}
CoordinatorProtocol:
public protocol CoordinatorProtocol {
func openVC1()
func openVC2()
}
My ViewModel1 calling VC2 through coordinatorDelegate:
func openVC2() {
coordinator.openVC2()
}
What I do when I finish ViewController2 and send user back do VC1:
navigationController?.dismiss(animated: true, completion: nil)
You need to to assign delegate value from prepare. Or you can assign delegate with initialize RedScreenVC(self) from your ViewController if u don't want to use storyboard/xib.
import UIKit
class ViewController: UIViewController, NavDelegate {
func navigate(text: String, isShown: Bool) {
print("text: \(text) isShown: \(isShown)")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "RedScreenVC") {
let redScreenVC = segue.destination as? RedScreenVC
redScreenVC?.delegate = self
}
}
#IBAction func nextPageButtonEventLustener(_ sender: Any) {
performSegue(withIdentifier: "RedScreenVC", sender: sender)
}
}
import UIKit
protocol NavDelegate {
func navigate(text: String, isShown: Bool)
}
class RedScreenVC: UIViewController {
weak var delegate: NavDelegate?
var redView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
var navigateButton: UIButton = {
let button = UIButton(frame: CGRect(x: 200, y: 350, width: 150, height: 50))
button.setTitle("Navigate", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.backgroundColor = .blue
return button
}()
#objc func buttonAction(){
if self.redView.backgroundColor == .gray {
self.redView.backgroundColor = .systemPink
}
self.delegate.navigate(text:"", isShown: true)
}
override func viewDidLoad() {
navigateButton.layer.cornerRadius = 25
redView.backgroundColor = UIColor.gray
delegate.navigate(text: "Navigation Success", isShown: true)
view.addSubview(redView)
view.addSubview(navigateButton)
}
}
If you do not want to use storyboard.
let redScreenVC = RedScreenVC()
redScreenVC.delegate = self
class RedScreenVC: UIViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init() {
super.init(nibName: nil, bundle: nil)
self.initialize()
}
func initialize() {
self.view.backgroundColor=CustomColor.PAGE_BACKGROUND_COLOR_1
//From here you need to create your email and password textfield
}
}

Call a View from another ViewController Swift

I have a ViewController that has a UIView set on top of it and a button that opens a popover to another ViewController. I want a button on the popover view controller to set the UIView to disable. How do I reference the UIView from the first view controller from a button in the second view controller?
EDIT:
Below is code that I use to call the popover view controller. Notice how I call dimView.isHidden = false from this first viewcontroller. I want to run dimView.isHidden = true from the popover view controller.
let popover = storyboard?.instantiateViewController(withIdentifier: "PopoverVC")
popover?.modalPresentationStyle = .popover
popover?.popoverPresentationController?.delegate = self as? UIPopoverPresentationControllerDelegate
popover?.popoverPresentationController?.sourceView = self.view
popover?.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popover?.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
dimView.isHidden = false
self.present(popover!, animated: false)
EDIT 2:
Below is my popover view controller. Since it is not called PopoverVC. I updated the answer to include let popover = storyboard?.instantiateViewController(withIdentifier: "PopoverVC") as! PopOverViewController but still no luck.
import UIKit
var parentController:UIViewController?
class PopOverViewController: 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 closeButton(_ sender: Any) {
self.dismiss(animated: false, completion: nil)
}
}
EDIT 3:
class ViewController: FormViewController {
override func viewWillAppear(_ animated: Bool) {
dimView.isHidden = true
}
#IBOutlet weak var dimView: UIView!
You can pass a reference to your current view controller when presenting your PopoverVC and then you can access its view from PopoverVC. Just create a property in PopoverVC that can store the reference, like var parentController:UIViewController?
let popover = storyboard?.instantiateViewController(withIdentifier: "PopoverVC") as! PopoverViewController
popover?.modalPresentationStyle = .popover
popover?.popoverPresentationController?.delegate = self as? UIPopoverPresentationControllerDelegate
popover?.popoverPresentationController?.sourceView = self.view
popover?.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popover?.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
popover?.dimView = self.dimView
dimView.isHidden = false
self.present(popover!, animated: false)
PopOverViewController:
class PopOverViewController: UIViewController {
var dimView:UIView?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func closeButton(_ sender: Any) {
self.dismiss(animated: false, completion: nil)
}
}

UIVisualEffectView creating unwanted shadow while presenting new view

In my custom presentation transition I've created a new view controller which will pre presented on top of the current active view controller (see screenshot). Somehow there's a shadow behind the blue view controller and I have no idea where it's coming from. Is there a way to stop getting that shadow?
The project is completely empty and has only 2 empty view controllers.
This is the code I'm using:
class ViewController: UIViewController {
let transitionDelegate = TransitionManager()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellowColor()
let button = UIButton(type: .System)
button.frame = CGRectMake(10, 10, 50, 50)
button.addTarget(self, action: "test:", forControlEvents: .TouchUpInside)
button.backgroundColor = UIColor.redColor()
view.addSubview(button)
}
func test(sender: UIButton) {
let destination = UIViewController()
destination.view.backgroundColor = .blueColor()
destination.transitioningDelegate = transitionDelegate
destination.modalPresentationStyle = .Custom
presentViewController(destination, animated: true, completion: nil)
}
}
The code for presenting the view:
class PresentingTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.3
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let presented = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let container = transitionContext.containerView()!
let durations = transitionDuration(transitionContext)
presented.view.alpha = 0
container.addSubview(presented.view)
UIView.animateWithDuration(durations, animations: { presented.view.alpha = 1 }) { transitionContext.completeTransition($0) }
}
}
The code for handling the presenting view controller:
class PresentationController: UIPresentationController {
var background: UIView!
override init(presentedViewController: UIViewController, presentingViewController: UIViewController) {
super.init(presentedViewController: presentedViewController, presentingViewController: presentingViewController)
prepareBackground()
}
func prepareBackground() {
self.background = UIView(frame: presentingViewController.view.bounds)
let blur = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
blur.frame = background.bounds
blur.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
background.addSubview(blur)
let tapRecognizer = UITapGestureRecognizer(target: self, action: "backgroundTapped:")
background.addGestureRecognizer(tapRecognizer)
}
func backgroundTapped(tapRecognizer: UITapGestureRecognizer) {
presentingViewController.dismissViewControllerAnimated(true, completion: nil)
}
override func presentationTransitionWillBegin() {
let container = containerView!
background.frame = container.bounds
background.alpha = 0.0
container.insertSubview(background, atIndex: 0)
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({ _ in self.background.alpha = 1.0 }, completion: nil)
}
override func dismissalTransitionWillBegin() {
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({ _ in self.background.alpha = 0.0 }, completion: nil)
}
override func frameOfPresentedViewInContainerView() -> CGRect {
return containerView!.bounds.insetBy(dx: 100, dy: 100)
}
override func containerViewWillLayoutSubviews() {
background.frame = containerView!.bounds
presentedView()!.frame = frameOfPresentedViewInContainerView()
}
}