Why is doubleAction working only when it's handled in a view controller class? - swift

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
}

Related

Unable to add constraints to my NSTableView in Cocoa Application

Below is my code in view controller
import Cocoa
class ViewController : NSViewController, NSTableViewDelegate, NSTableViewDataSource {
let stepperBtn: NSSegmentedControl = {
let stepperBtn = NSSegmentedControl()
stepperBtn.segmentCount = 2
stepperBtn.setImage(NSImage(systemSymbolName: "plus.circle", accessibilityDescription: "plus"), forSegment: 0)
stepperBtn.setImage(NSImage(systemSymbolName: "minus.circle", accessibilityDescription: "minus"), forSegment: 1)
stepperBtn.translatesAutoresizingMaskIntoConstraints = false
return stepperBtn
}()
var tableView: NSTableView {
let tablview = NSTableView()
tablview.translatesAutoresizingMaskIntoConstraints = false
return tablview
}
override func loadView() {
self.view = NSView(frame: NSMakeRect(0.0, 0.0, 550.0, 300.0))
tableView.delegate = self
tableView.dataSource = self
self.view.addSubview(tableView)
self.view.addSubview(stepperBtn)
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
}
override func viewDidLayout() {
}
override func viewDidLoad() {
super.viewDidLoad()
}
func numberOfRows(in tableView: NSTableView) -> Int {
return 5
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
I get below error : "NSTableView:0x7f9beb851600.top"> and <NSLayoutYAxisAnchor:0x600000bcc400 "NSView:0x7f9beaa184e0.top"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.
How to add constraints programatically to NSTableView

How to navigate from one View Controller to the other?

I want to navigate from one View Controller to another.
let vc = SecondViewController()
I have tried until now :
vc.modalPresentationController = .fullScreen
self.present(vc, animated: true) //self refers to the main view controller
Im trying to open a new ViewController when the users manages to register or to log in.I am new to software developing, and I want to ask, is this the best method to navigate from one ViewController to another, im asking because as I can see the mainViewController is not deinit(). I have found other similar questions and tried the answers, the problem is with the:
self.navigationController?.pushViewController
it doesn't work because I don't have any storyboard.
The question is it is right to navigate as explained above?
Thanks,
Typically when you are doing login you would use neither push or present. There are multiple ways of handling this, but the easiest is to embed in some parent (root) VC. Here is an example:
class ViewController: UIViewController {
private var embeddedViewController: UIViewController! {
didSet {
// https://developer.apple.com/documentation/uikit/view_controllers/creating_a_custom_container_view_controller
// Add the view controller to the container.
addChild(embeddedViewController)
view.addSubview(embeddedViewController.view)
// Create and activate the constraints for the child’s view.
embeddedViewController.view.translatesAutoresizingMaskIntoConstraints = false
embeddedViewController.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
embeddedViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
embeddedViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
embeddedViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// Notify the child view controller that the move is complete.
embeddedViewController.didMove(toParent: self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let loginVC = LoginViewController()
loginVC.delegate = self
embeddedViewController = loginVC
}
}
extension ViewController: LoginDelegate {
func didLogin() {
embeddedViewController = MainViewController()
}
}
protocol LoginDelegate: AnyObject {
func didLogin()
}
class LoginViewController: UIViewController {
private lazy var loginButton: UIButton = {
let button = UIButton()
button.setTitle("Login", for: .normal)
button.addTarget(self, action: #selector(didTapLoginButton), for: .touchUpInside)
return button
}()
weak var delegate: LoginDelegate?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(loginButton)
view.backgroundColor = .red
loginButton.translatesAutoresizingMaskIntoConstraints = false
loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loginButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc private func didTapLoginButton() {
delegate?.didLogin()
}
}
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
}
}

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

Swift using function from delegated class as selector crashes

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)

Add a NSVisualEffectView below NSTextView

I want to add a NSVisualEffectView inside my NSTextView, however when I add it, the text is below the NSVisualEffectView, so, how can i add the NSVisualEffectView below the text?
My code for OS X:
class myTextView: NSTextView {
override func awakeFromNib() {
let visualEffectView = NSVisualEffectView(frame: NSMakeRect(20, 20, 30, 18))
visualEffectView.material = NSVisualEffectMaterial.Dark
visualEffectView.blendingMode = NSVisualEffectBlendingMode.BehindWindow
self.addSubview(visualEffectView)
}
}
Maybe this will help you:
class MyTextView: NSTextView {
lazy var visualEffectView: NSVisualEffectView = {
let visualEffectView = NSVisualEffectView()
visualEffectView.material = NSVisualEffectMaterial.Dark
visualEffectView.blendingMode = NSVisualEffectBlendingMode.BehindWindow
return visualEffectView
}()
override func awakeFromNib() {
super.awakeFromNib()
self.drawsBackground = false
}
override func viewDidMoveToSuperview() {
super.viewDidMoveToSuperview()
if let containerView = self.superview?.superview?.superview {
containerView.addSubview(self.visualEffectView, positioned: .Below, relativeTo: self)
}
}
override func resizeSubviewsWithOldSize(oldSize: NSSize) {
super.resizeSubviewsWithOldSize(oldSize)
if let superview = self.superview?.superview {
self.visualEffectView.frame = superview.frame
}
}
}
It's a text view in a scroll view and clip view so you have to get its superview.
self.sendSubviewToBack(visualEffectView)
But the visual effect needs to be applied to the parent container, not the text view itself, so if I'm understanding your code correctly you need to move this whole thing up to whatever the parent view is.