Search code examples
swiftpopovernstextfield

Swift: NSTextfield and popover with suggested list of tags


I'm trying to do something like this: what I want

This is what I have now: screenshot of my current app

How I'm doing this now:

I have MainViewController with input field: NSTextField. Using controlTextDidChange I'm checking if input field contains # character.

If yes - I'm opening popover using PopoverViewController. In the PopoverViewController I have NSTableView that displays a list of available tags.

By clicking on the row it should add selected tag to the input fields and close the popover. So here I even have to implement 2 way of communication between 2 view controllers.

Questions:

  1. I feel that I'm doing something wrong. The logic of this tags-popover looks too complicated for me. Maybe an easier solution exists, but I just don't know how to do this.
  2. What the best way to display the list of available tags in NSTextField? Should I use a popover with another view controller?
  3. How to communicate best in this case between 2 different view controllers? (I have to send InputField value to PopoverViewController, filter results there and display them. Then, I have to send back to MainViewController selected tag)

My code:

Here is some parts of my code if you want to see it in more details:

MainViewController:

class ViewController: NSViewController, NSTextFieldDelegate {

@IBOutlet weak var InputField: NSTextField!

override func viewDidLoad() {
    super.viewDidLoad()

    table.delegate = self
    table.dataSource = self
    InputField.delegate = self
}

func controlTextDidChange(_ sender: Notification) {

    if InputField.stringValue.contains("#") {

        let controller = ToolTipVC.Controller()
         let popover = NSPopover()

         popover.contentViewController = controller
         popover.behavior = .transient
         popover.animates = true

         popover.show(relativeTo: InputField.bounds, of: InputField!, preferredEdge: NSRectEdge.maxY)
    } 
}

ToolTipViewController:

import Cocoa
import EventKit


class ToolTipVC: NSViewController, NSTableViewDataSource, NSTableViewDelegate {

    @IBOutlet weak var ToolTipTable: NSTableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        ToolTipTable.delegate = self
        ToolTipTable.dataSource = self
    }

    func numberOfRows(in tableView: NSTableView) -> Int {
        return ReminderLists.count
    }

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {

        guard let cell = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? NSTableCellView else { return nil }

        cell.textField?.stringValue = "Row #\(row): " + ReminderLists[row].title
        return cell
    }
}

extension ToolTipVC {
  static func Controller() -> ToolTipVC {

    let storyboard = NSStoryboard(name: "Main", bundle: nil)
    let identifier = NSStoryboard.SceneIdentifier("ToolTipId")
    guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? ToolTipVC else {
      fatalError("Why cant i find viewcontroller? - Check Main.storyboard")
    }

    return viewcontroller
  }
}

I appreciate your feedback and advises. Thank you in advance!

P.S.: I'm not a developer, I'm a Product Manager. This is my pet-project, so I know that my code is awful and has a lot of mistakes. Please, don't judge me hard :)


Solution

  • At first glance it looks like when you call this line:

    popover.show(relativeTo: InputField.bounds, of: InputField!, preferredEdge: NSRectEdge.maxY)
    

    preferredEdge is set to NSRectEdge.maxY

    Try changing this from Y to X

    popover.show(relativeTo: InputField.bounds, of: InputField!, preferredEdge: NSRectEdge.maxX)
    

    Is there a reason you are usin NSPopover?

    If that doesnt work try this:

       let controller = MyPopViewController()
       controller.modalPresentationStyle = UIModalPresentationStyle.popover
       let popController = controller.popoverPresentationController
       popController?.permittedArrowDirections = .any
       popController?.delegate = self
       popController?.sourceRect = //set the frame here
       popController?.sourceView = //set the source view here
       self.present(controller, animated: true, completion: nil)
    

    Here is another approach, this will centre the pop over over a button. Which is what you are trying to do i am assuming with no up arrow? Is that correct? So you can hit the filters button on the right side whihc you have and this pop over will appear centred.

    @IBAction func buttonTap(sender: UIButton) {
    
        // get a reference to the view controller for the popover
        let popController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "popoverId")
    
        // set the presentation style
        popController.modalPresentationStyle = UIModalPresentationStyle.popover
    
        // set up the popover presentation controller
        popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
        popController.popoverPresentationController?.delegate = self
        popController.popoverPresentationController?.sourceView = sender // button
        popController.popoverPresentationController?.sourceRect = sender.bounds
    
        // present the popover
        self.present(popController, animated: true, completion: nil)
    }