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")
}
}
}
Related
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
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
}
I have 3 classes:
ChatLogControoller
GetImageFromLibraty(NSObject class)
ImagePreviewViewController
I want to press a clip from the first VC, then open the media library to pick an image. Then the selected image is passed to the third VC as a previewController. Then if I select 'done' I want to pass it to the first VC.
1st VC
class ChatLogControoller: UICollectionViewController, UICollectionViewDelegateFlowLayout, NSFetchedResultsControllerDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate, DataSentDelegate {
func recievePhoto(data: UIImage) {
imageFromView = data
print("-------\(imageFromView = data)")
}
override func viewDidLoad() {
super.viewDidLoad()
let vc = ImagePreviewController()
self.vc.delegate = self
}
2nd class its just picker of image, so i pass image to 3rd VC and this image appears on imageView of 3rd VC successfully!
my 3rd VC
protocol DataSentDelegate {
func recievePhoto(data: UIImage)
}
class PreviewController: UIViewController, UIScrollViewDelegate {
var delegate : DataSentDelegate? = nil
var aImageView: UIImageView!
var aImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Add", style: .plain, target: self, action: #selector(actionSend))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(actionBack))
}
#objc func actionBack() {
dismiss(animated: false, completion: nil)
}
#objc func actionSend() {
let data = aImageView.image
delegate?.recievePhoto(data: data!)
dismiss(animated: true, completion: nil)
}
You need to create one more protocol in your SecondViewController to Pass that delegate from ThirdViewController to FirstViewController.
FirstViewController:
import UIKit
class ViewController: UIViewController, DataSentDelegate, dataSentDelegate {
#IBOutlet weak var imagefromThirdVC: UIImageView!
var thirdVCImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonTapped(_ sender: Any) {
let vc = storyboard?.instantiateViewController(withIdentifier: "ViewController2") as! ViewController2
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
func goToThirdVC() {
let vc = storyboard?.instantiateViewController(withIdentifier: "ViewController3") as! ViewController3
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
func recievePhoto(data: UIImage) {
thirdVCImage = data
imagefromThirdVC.image = thirdVCImage
}
}
SecondViewController:
import UIKit
protocol dataSentDelegate {
func goToThirdVC()
}
class ViewController2: UIViewController {
#IBOutlet weak var passingImage: UIImageView!
var delegate: dataSentDelegate? = nil
var images: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
images = UIImage(named: "screen")
}
#IBAction func actionButton(_ sender: Any) {
self.delegate?.goToThirdVC()
}
}
ThirdViewController:
import UIKit
protocol DataSentDelegate {
func recievePhoto(data: UIImage)
}
class ViewController3: UIViewController {
var delegate: DataSentDelegate? = nil
#IBOutlet weak var passedImageView: UIImageView!
var passedImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
passedImage = UIImage(named: "screen")
passedImageView.image = passedImage
}
#IBAction func action(_ sender: Any) {
let data = passedImageView.image
delegate?.recievePhoto(data: data!)
// delegate?.goToFirstVC()
guard let viewControllers = self.navigationController?.viewControllers else {
return
}
for firstViewController in viewControllers {
if firstViewController is ViewController {
self.navigationController?.popToViewController(firstViewController, animated: true)
break
}
}
}
}
I have a view controller MyViewController:
class MyViewController: NSViewController {
private let componentList = ComponentList()
override func loadView() {
componentList.createView(view)
componentList.myTableView.doubleAction = #selector(doubleClickOnRow)
}
#objc func doubleClickOnRow() {
print("some row clicked = \(componentList.myTableView.clickedRow)")
}
}
This double click action works without problem. However, when I try to put this double click action inside ComponentList, it's not working (action function is not called):
class ComponentList: NSObject, NSTableViewDelegate, NSTableViewDataSource {
let myTableView = NSTableView()
override func createView(view: NSView) {
let scrollView = NSScrollView()
view.addSubview(scrollView)
scrollView.documentView = myTableView
// set up some constraints, ignore here...
myTableView.delegate = self
myTableView.dataSource = self
myTableView.doubleAction = #selector(doubleClickOnRow)
}
#objc func doubleClickOnRow() {
print("some row clicked = \(myTableView.clickedRow)")
// never being called, why is that?
}
}
Why isn't the double action handling in ComponentList not working? Am I missing something here?
You need to set both target and doubleAction to make it work with ComponentList
override func createView(view: NSView) {
let scrollView = NSScrollView()
view.addSubview(scrollView)
scrollView.documentView = myTableView
// set up some constraints, ignore here...
myTableView.delegate = self
myTableView.dataSource = self
myTableView.doubleAction = #selector(doubleClickOnRow)
myTableView.target = self // self here is ComponentList
}
I have a custom view class, MyView, which inherits from UIView, and it contains a text field. I have added a delegate variable to this class which represents an instance of my ViewController class. This controller contains a function which I want to use as a selector in addTarget inside MyView:
class ViewController: UIViewController, UITextFieldDelegate {
let my_view = MyView()
override func viewDidLoad() {
super.viewDidLoad()
self.my_view.delegate = self
self.my_view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.my_view)
// set up constraints
}
func handleDatePicker(sender: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd h:mm a"
self.my_view.time_text_field.text = "\(dateFormatter.string(from: sender.date))"
}
}
class MyView: UIView {
weak var delegate: ViewController! {
didSet {
self.time_text_field.delegate = self.delegate
}
}
lazy var time_text_field: UITextField = {
let text_field = UITextField()
let date_picker = UIDatePicker()
date_picker.addTarget(self, action: #selector(self.delegate.handleDatePicker(sender:)), for: .valueChanged)
text_field.inputView = date_picker
text_field.translatesAutoresizingMaskIntoConstraints = false
return text_field
}()
init() {
self.addSubview(self.time_text_field)
// set up constraints
}
}
When the function handleDatePicker gets called, the app crashes. However, when I move the function into the MyView class, the app no longer crashes:
class ViewController: UIViewController, UITextFieldDelegate {
let my_view = MyView()
override func viewDidLoad() {
super.viewDidLoad()
self.my_view.delegate = self
self.my_view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.my_view)
// set up constraints
}
}
class MyView: UIView {
weak var delegate: ViewController! {
didSet {
self.time_text_field.delegate = self.delegate
}
}
lazy var time_text_field: UITextField = {
let text_field = UITextField()
let date_picker = UIDatePicker()
date_picker.addTarget(self, action: #selector(self.handleDatePicker(sender:)), for: .valueChanged)
text_field.inputView = date_picker
text_field.translatesAutoresizingMaskIntoConstraints = false
return text_field
}()
init() {
self.addSubview(self.time_text_field)
// set up constraints
}
func handleDatePicker(sender: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd h:mm a"
self.time_text_field.text = "\(dateFormatter.string(from: sender.date))"
}
}
Why does the app crash when the function used in #selector comes from the delegated class? Thanks.
The problem is that I was writing
date_picker.addTarget(self, action: #selector(self.delegate.handleDatePicker(sender:)), for: .valueChanged)
Instead of self for the first parameter, I should be using self.delegate:
date_picker.addTarget(self.delegate, action: #selector(self.delegate.handleDatePicker(sender:)), for: .valueChanged)