Delete Nsmenu item with specific tag - swift

I use this function to create a NSMenuitems. They all get tagged with 2.
func addToComputerInfoMenu (title: String)
{
let addToComputerItem : NSMenuItem = NSMenuItem(title: "\(title)" , action: #selector(openWindow), keyEquivalent: "")
addToComputerItem.attributedTitle = NSAttributedString(string: "\(title)", attributes: [NSFontAttributeName: NSFont.systemFontOfSize(14), NSForegroundColorAttributeName: NSColor.blackColor()])
addToComputerItem.tag = 2
addToComputerItem.enabled = true
computerInfoMenu.addItem(addToComputerItem)
}
I would like to programmatically delete all items with "2" tag. I tried using .itemWithTag and .indexOfItemWithTag. I can't seem to iterate through the list.
let itemswithindex2 = computerInfoMenu.itemWithTag(2)

I found a way to accomplish my goal. Not sure its the best solution but it works.
for item in computerInfoMenu.itemArray
{
if (item.tag) == 2
{
computerInfoMenu.removeItem(item)
}
}

Related

How To Detect Which NSMenuItem Was Selected

Given that I have a menuBar app with 3 items on a subMenu:
let delaySubMenu = NSMenu()
delaySubMenu.addItem(NSMenuItem(title: "5", action: #selector( setReminder(_:)), keyEquivalent: ""))
delaySubMenu.addItem(NSMenuItem(title: "10", action: #selector(setReminder(_:)), keyEquivalent: ""))
delaySubMenu.addItem(NSMenuItem(title: "15", action: #selector(setReminder(_:)), keyEquivalent: ""))
How do I detect which of my delaySubMenu items has been selected without making a unique setReminder function for each?
Thanks
The action selector will receive the sender object just like it would if you were using Interface Builder. So your setReminder(_:) selector could have the signature:
func setReminder(_ sender: Any) {
// Coerce sender to NSMenuItem and use it to make your decisions
}
or:
func setReminder(_ sender: NSMenuItem) {
// Don't do any coercion work you don't need to do…
}
You could also set the tag property of the NSMenuItem to your delay values. The tag property is an Int type so a good match for your values.
As you are creating multiple entries you could use a for in loop to traverse an array or dictionary, creating a new NSMenuItem for each entry. So we could change your original code to something like this example where I use a dictionary:
let delaySubMenu = NSMenu()
let delays = ["5 Minutes" : 5, "10 Minutes" : 10, "15 Minutes" : 15] // This is a dictionary of String:Int
for (titleKey, value) in delays {
let menuItem = NSMenuItem(title: titleKey, action: #selector(setReminder(_:)), keyEquivalent: nil)
menuItem.tag = value
delaySubMenu.addItem(menuItem)
}
func setReminder(_ sender: NSMenuItem) {
let delayValue = sender.tag // delayValue is a Int by inference from tag
// Do something with your delay value
}
disclaimer: this is just cut and paste in the browser so it may needs some tweaking to actually work.

Storing Button Pressed In Swift

I have collection view where you can select 4 buttons, it is like a quiz with A, B, C, D. I need to store which one they clicked before they go to the next question (They will swipe to go to the next question since it is a collection view) The controller looks like this:
First: Essentially the code used to display the image above, I have created a database which is parsed using this:
struct Question {
let fact: String
let question: String
let answers: [String: String]
let correctAnswer: String
let revenue: String
init?(with dictionary: [String: Any]) {
guard let fact = dictionary["fact"] as? String,
let question = dictionary["question"] as? String,
let answerA = dictionary["answer_a"] as? String,
let answerB = dictionary["answer_b"] as? String,
let answerC = dictionary["answer_c"] as? String,
let answerD = dictionary["answer_d"] as? String,
let revenue = dictionary["revenue"] as? String,
let correctAnswer = dictionary["correctAnswer"] as? String else { return nil }
self.fact = fact
self.question = question
self.revenue = revenue
var answersDict = [String: String]()
answersDict["answer_a"] = answerA
answersDict["answer_b"] = answerB
answersDict["answer_c"] = answerC
answersDict["answer_d"] = answerD
self.answers = answersDict
self.correctAnswer = correctAnswer
}
Second: Then I display using this code:
extension QuestionCell {
func configure(with model: Question) {
factLabel.text = model.fact
questionLabel.text = model.question
revenueLabel.text = model.revenue
let views = answersStack.arrangedSubviews
for view in views {
view.removeFromSuperview()
}
for (id, answer) in model.answers {
print(index)
print(id)
let answerLabel = UILabel()
answerLabel.text = answer
answersStack.addArrangedSubview(answerLabel)
let answerButton = UIButton()
let imageNormal = UIImage(named: "circle_empty")
answerButton.setImage(imageNormal, for: .normal)
let imageSelected = UIImage(named: "circle_filled")
answerButton.setImage(imageSelected, for: .selected)
answerButton.setTitleColor(.black, for: .normal)
answerButton.addTarget(self, action: #selector(answerPressed(_:)), for: .touchUpInside)
answersStack.addArrangedSubview(answerButton)
}
}
}
Is there a way to store the button I clicked? Thanks
Well there is one sure shot way out of this situation.
Make a custom cell for the collectionView.
Add button outlets and action the the customCell's class
Create and make use of delegate methods in customCell's class and when implementing the customCell in your ViewController set the delegate to
self.
Trigger the delegate methods when button actions are done (inside your custom cell).
Provide your customCell the current indexpath when using it in cellForItemAt method.
Make use of that indexPath to decide which button was triggered.
You should be thinking something close to this approach for a robust solution.
The way I've handled this in the past is to use the tag on a UIButton and just keep track of which tag is currently selected. With this approach I can use the same IBAction for every button and all I need to do is pull the tag from the sender in the function body. While maybe not as flexible and robust is an approach using subclassing, it's a bit quicker to implement.
First set your tags when you create your buttons (I use 100-104 to avoid conflicts with other buttons). Since you're creating your buttons in a CollectionView, you'll need to set the tag in your configure() function:
func configure(with model: Question) {
...
for (id, answer) in model.answers {
...
answerButton.setImage(imageSelected, for: .selected)
answerButton.tag = index
answerButton.setTitleColor(.black, for: .normal)
answerButton.addTarget(self, action: #selector(answerPressed(_:)), for: .touchUpInside)
}
}
Create an instance variable:
var selectedAnswerIndex = -1
Then assign this IBAction to each of you buttons:
func answerPressed(_ sender: UIButton){
selectedAnswerIndex = sender.tag
hilightNewOne(sender: sender)
}

Collection View Storing Button Pressed Swift

I have a collection view where it needs to store which button is pressed. This is like a quiz where they have options A, B, C, D. The problem is that it will select the item with this code:
extension QuestionCell {
func configure(with model: Question) {
factLabel.text = model.fact
questionLabel.text = model.question
revenueLabel.text = model.revenue
let views = answersStack.arrangedSubviews
for view in views {
view.removeFromSuperview()
}
for (id, answer) in model.answers {
print(index)
let answerLabel = UILabel()
answerLabel.text = answer
answersStack.addArrangedSubview(answerLabel)
let answerButton = UIButton()
answerButton.tag = index
let imageNormal = UIImage(named: "circle_empty")
answerButton.setImage(imageNormal, for: .normal)
let imageSelected = UIImage(named: "circle_filled")
answerButton.setImage(imageSelected, for: .selected)
answerButton.setTitleColor(.black, for: .normal)
answerButton.addTarget(self, action: #selector(answerPressed(_:)), for: .touchUpInside)
answersStack.addArrangedSubview(answerButton)
}
}
But then if you swipe a couple times and then come back to this question the image will be white again. How can I fix this problem. Also I attached how the screen looks.
This is how it looks like
~ Thanks
You could solve this with an Answer class like this. In case you're new to classes, I should point out that you won't want to write this code inside another class definition (like your ViewController class.)
class Answer{
var wasSelected = false
var text : String
init(text : String) {
self.text = text
}
}
class ButtonWithAnswer : UIButton{
var answer : Answer?
}
But now your model.answers will look like this, and will be of type [String : Answer]:
model.answers = ["id1" : Answer(text : "answer 1"), "id2" : Answer(text: "answer 2"), "id3":Answer(text: "answer 3")]
The idea is that instead of having model.answers contain strings, they contain a new custom type that we made up called Answer that can keep track of the text for the answer (which is a property called text of type String) as well as whether or not it was selected (which is a property called wasSelected of type Bool). Your for loop will now look like this:
for (id, answer) in model.answers {
let answerLabel = UILabel()
answerLabel.text = answer.text //this comes from our new class definition
answersStack.addArrangedSubview(answerLabel)
//Change this to our new custom button subclass
let answerButton = ButtonWithAnswer()
//this line straight up won't work since you got rid of index
//answerButton.tag = index
//use this instead
answerButton.answer = answer
let imageNormal = UIImage(named: "circle_empty")
answerButton.setImage(imageNormal, for: .normal)
let imageSelected = UIImage(named: "circle_filled")
answerButton.setImage(imageSelected, for: .selected)
//make it so that any answers that were previously selected are now selected when the view reloads
answerButton.isSelected = answer.wasSelected
answerButton.setTitleColor(.black, for: .normal)
answerButton.addTarget(self, action: #selector(answerPressed(_:)), for: .touchUpInside)
answersStack.addArrangedSubview(answerButton)
}
There's one last piece of this. We need to make sure that we make our answer.wasSelected equal to true when we tap the button that corresponds with that answer. So in answerPressed we should do something like this.
func answerPressed(_ sender : UIButton){
let answerForButton = (sender as! ButtonWithAnswer).answer!
answerForButton.wasSelected = true
}
That should about cover it. If this looks like something totally new then you should look up tutorials on classes, objects, and init methods in Swift before you dive in or ask a programmer for help in person. But I explained it about as well as I can without actually being there in person to give you a complete walkthrough.

Using NSPredicate to add enable users to 'Favourite' objects, which will then appear in a separate TableView (without CoreData!)

I currently have a tableView which is parsing and displaying data from a JSON file. When a cell is pressed, then the user is taken to a detailView screen to view further information.
I am now trying to set up a Favourite button within the detailView, so that users can press a button which will add the object to a favouriteView (this will be set up exactly the same as the tableView, but only show locations which have been favourited.
My understanding is that the best way to achieve this is to use NSPredicate. I have been looking around for hours and I can't seem to find a solution that does not use CoreData (although I have found a source which says that CoreData is not necessary). I would like to achieve the Favourite function without using CoreData.
I already have a favourite button set up in the detailView, which sets a variable isFavourite from false to true. Please see all the relevant code below.
Sample JSON object:
{
"locations": [
{
"id": "0001",
"name": "Helensburgh Tunnels",
"type": "Tunnels, Beach, Views",
"location": "Helensburgh, South Coast",
"image": "Helensburgh-Tunnels.jpg",
"activity": "Walking, photography, tunnelling",
"isVisited": false,
"isFavourite": false,
"latitude": -34.178985,
"longitude": 150.992867
}
]
}
The favourite button I have set up in the detailView:
#IBOutlet weak var favouriteButton: UIButton!
#IBAction func favouriteButtonTapped(_ sender: Any) {
self.location.isFavourite = self.location.isFavourite ? false : true
print(location.isFavourite)
// Favourite button
if location.isFavourite == false {
favouriteButton?.setImage(UIImage(named: "starLine.png"), for: .normal)
} else {
favouriteButton?.setImage(UIImage(named: "starFull.png"), for: .normal)
}
}
How can I take all the objects which have been set to isFavourite = true and display them in a tableView?
This is a section of the FavouriteLocationsViewController class, what do I need to fix here to make it functional?
class FavouriteLocationsViewController: UITableViewController {
var locations = [Location]()
override func viewDidLoad() {
super.viewDidLoad()
// Remove the title of the back button
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
if let locationJson = readLocation(){
if let locationArray = locationJson["locations"] as? [[String:Any]]{
for location in locationArray{
locations.append(Location.init(locationInfo: location))
}
print(locations.count)
self.tableView.reloadData()
}
}
}
// Filter to show only favourite locations
func filterArrayOfDictonaryWithKeyValueUsingPredicate(arrOfDict: NSArray, keyOfDict: String, strSearchText: String) -> NSArray{
let favouritePredicate = NSPredicate(format: "isFavourite == true")
let arrResult = arrOfDict.filtered(using: favouritePredicate)
return arrResult as NSArray
}
}
you want to show only favourite data there?
you have array of dictonary as per you are showing above.
you can filter that array using predicate.
and you can get only object which isFavourite == "True"
use below function pass array of Location, Pass keyofDict - isFavourite
strSearchText - for your example use true
func filterArrayOfDictonaryWithKeyValueUsingPredicate(arrOfDict: NSArray, keyOfDict: String, strSearchText: String) -> NSArray{
let searchPredicate = NSPredicate(format: "\(keyOfDict) CONTAINS[C] %#", strSearchText)
let arrResult = arrOfDict.filtered(using: searchPredicate)
return arrResult as NSArray
}

Contextual menu with search result for a NSSearchField

I would like to have a contextual menu that shows search results as text is entered in a search field. This is an
image of the default mail app in OS X that does this. I know how to filter an array of strings according to the search request of the user, but I do not know how to display it this way. I am using Swift and for a Cocoa application. Any help is appreciated.
Building from the previous answer, here is a simple Swift 3 class which you can use to automatically handle recent searches. You can add it as a custom class in your storyboard, or directly.
It will look like this:
import Cocoa
class RecentMenuSearchField: NSSearchField {
lazy var searchesMenu: NSMenu = {
let menu = NSMenu(title: "Recents")
let recentTitleItem = menu.addItem(withTitle: "Recent Searches", action: nil, keyEquivalent: "")
recentTitleItem.tag = Int(NSSearchFieldRecentsTitleMenuItemTag)
let placeholder = menu.addItem(withTitle: "Item", action: nil, keyEquivalent: "")
placeholder.tag = Int(NSSearchFieldRecentsMenuItemTag)
menu.addItem( NSMenuItem.separator() )
let clearItem = menu.addItem(withTitle: "Clear Menu", action: nil, keyEquivalent: "")
clearItem.tag = Int(NSSearchFieldClearRecentsMenuItemTag)
let emptyItem = menu.addItem(withTitle: "No Recent Searches", action: nil, keyEquivalent: "")
emptyItem.tag = Int(NSSearchFieldNoRecentsMenuItemTag)
return menu
}()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
initialize()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initialize()
}
//create menu
private func initialize() {
self.searchMenuTemplate = searchesMenu
}
}
NSSearchField searchMenuTemplate (NSMenu) contains Menu Items (NSMenuItem) with specific tags used by the Search Field to populate the Menu.
The recentSearches Array here is just to pass additional String used in complement of Recents Search strings and is not required (I thought that it was to store recent search but no). NSSearchField also clear this Array when the user clears Recents Search.
You can also configure a Menu with category, more info here:
Configuring a Search Menu — Apple Developer
Example:
#IBOutlet weak var search: NSSearchField!
/// Array of string containing additional recents search (custom search)
var recentSearches = [String]()
/// Search Field Recents Menu
lazy var searchesMenu: NSMenu = {
let menu = NSMenu(title: "Recents")
let i1 = menu.addItem(withTitle: "Recents Search", action: nil, keyEquivalent: "")
i1.tag = Int(NSSearchFieldRecentsTitleMenuItemTag)
let i2 = menu.addItem(withTitle: "Item", action: nil, keyEquivalent: "")
i2.tag = Int(NSSearchFieldRecentsMenuItemTag)
let i3 = menu.addItem(withTitle: "Clear", action: nil, keyEquivalent: "")
i3.tag = Int(NSSearchFieldClearRecentsMenuItemTag)
let i4 = menu.addItem(withTitle: "No Recent Search", action: nil, keyEquivalent: "")
i4.tag = Int(NSSearchFieldNoRecentsMenuItemTag)
return menu
}()
override func viewDidLoad() {
super.viewDidLoad()
recentSearches = ["Toto","Titi","Tata"]
search.delegate = self
search.recentSearches = recentSearches
search.searchMenuTemplate = searchesMenu
}
You need to make an NSMenu Item with special tags that are placeholders so the search field knows where to put recent items, the clear action, etc.
Look at documentation for searchMenuTemplate, and the Tags : NSSearchFieldRecentsMenuItemTag etc.
Basically, make a contextual menu in IB. Drag your search field to use that menu as the searchMenuTemplate, and then populate menu items with the tags you want for clear, recent items, etc.