I have got a simple view in swift and put 9 buttons as a 3x3 grid onto it, now i need the tag for each button and don't know how to get each button so I can get the tag property using a for loop. Does anyone know how i can get the button? Is there a function to get a view at a specified location?
Frankenstein's answer with .subviews works fine, but you can even do it more swifter using the built-in function viewWithTag(_:):
override func viewDidLoad() {
super.viewDidLoad()
// ...
// targeted view must have been added to the subview by now
if let taggedView = view.viewWithTag(1) {
print("Got the view!")
}
// you can even try to cast directly
if let taggedButton = view.viewWithTag(1) as? UIButton {
print("Got the button!")
}
// if you insist on using a for loop, you could use it like this
let highestTag = 10
for i in 0...highestTag {
if let taggedButton = view.viewWithTag(i) as? UIButton {
// here you go
}
}
}
For both approaches keep in mind, that the view you're looking for has already been added as a subview beforehand, otherwise you won't be able to search for it
You can use the subviews property of UIView and loop through each subview to check for tag you need:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
for subview in view.subviews {
if subview.tag == 1, let button = subview as? UIButton {
print("Got the button I need.")
}
}
if let button = view.viewWithTag(1) as? UIButton {
print("Got the button I need.")
}
}
}
Update: You can also use viewWithTag to find you view with a particular tag from within your subviews.
Related
I have buttons in the storyboard that I put into a Referencing Outlet Collection. I'm using UITapGestureRecognizer and UILongPressGestureRecognizer for all of these buttons, but how can I print exactly which button gets tapped? Bellow is what I tried but doesn't work. I get an error that says "Value of type 'UILongPressGestureRecognizer' has no member 'tag'." I'm trying to build the button grid for the Minesweeper game. Thank you for your help.
class ViewController: UIViewController {
#IBOutlet var testButtons: [UIButton]! // There are 100 buttons in this array
override func viewDidLoad() {
super.viewDidLoad()
let testButtonPressed = UILongPressGestureRecognizer(target: self, action: #selector(testPressed))
testButtonPressed.minimumPressDuration = 0.5
// These indexes are just to test how to recognize which button gets pressed
testButtons[0].addGestureRecognizer(testButtonPressed)
testButtons[1].addGestureRecognizer(testButtonPressed)
}
#objc func testPressed(_ sender: UILongPressGestureRecognizer) {
print("Test button was pressed")
print(sender.tag) // THIS DOESN'T WORK, BUT CONCEPTUALLY THIS IS WHAT I WANT TO DO
}
This error occurs because UILongPressGestureRecognizer object has no tag property
You can access sender's button in a way like that:
#objc func testPressed(_ sender: UILongPressGestureRecognizer) {
guard let button = sender.view as? UIButton else { return }
print(button.tag)
}
I think that the best solution to handle button's actions is to add #IBAction
(you can add it like #IBOutlet with a minor change - set Action connection type)
And then in #IBAction block you cann access all button properties (like tag and others)
instead of using gesture I think it would be better to use #IBAction and connect the buttons With it here is a small example
UILongPressGestureRecognizer which is a subclass of UIGestureRecognizer, can be used only once per button or view. Because UILongPressGestureRecognizer has only a single view property. In your code, it will always be testButtons[1] calling the testPressed action. So you have to first modify the viewDidLoad code like this :-
for button in testButtons {
let testButtonPressed = UILongPressGestureRecognizer(target: self, action: #selector(testPressed))
testButtonPressed.minimumPressDuration = 0.5
button.addGestureRecognizer(testButtonPressed)
button.addGestureRecognizer(testButtonPressed)
}
Then you can access the button from testPressed like this (I hope you've already set the tag in the storyboard) :-
#objc func testPressed(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
if let button = sender.view as? UIButton {
print(button.tag)
}
}
}
You need to set tags before pressing!
On the viewDidLoad() method you must add something like:
testButtons.enumerated().forEach {
let testButtonPressed = UILongPressGestureRecognizer(target: self, action: #selector(testPressed))
testButtonPressed.minimumPressDuration = 0.5
$0.element.addGestureRecognizer(testButtonPressed)
$0.element.tag = $0.offset
}
And when the long press is receiving you need to get a tag from view not from the sender!
print(sender.view?.tag)
Since a gesture recognizer should only be associated with a single view, and doesn't directly support using an identity tag to match it with buttons. When creating an array of buttons for a keyboard, with a single gesture response function, I found it easier to use the gesture recognizer "name" property to identify the associated button.
var allNames: [String] = []
// MARK: Long Press Gesture
func addButtonGestureRecognizer(button: UIButton, name: String) {
let longPrssRcngr = UILongPressGestureRecognizer.init(target: self, action: #selector(longPressOfButton(gestureRecognizer:)))
longPrssRcngr.minimumPressDuration = 0.5
longPrssRcngr.numberOfTouchesRequired = 1
longPrssRcngr.allowableMovement = 10.0
longPrssRcngr.name = name
allNames.append(name)
button.addGestureRecognizer(longPrssRcngr)
}
// MARK: Long Key Press
#objc func longPressOfButton(gestureRecognizer: UILongPressGestureRecognizer) {
print("\nLong Press Button => \(String(describing: gestureRecognizer.name)) : State = \(gestureRecognizer.state)\n")
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
if let keyName = gestureRecognizer.name {
if allNames.contains(keyName) {
insertKeyText(key: keyName)
} else {
print("No action available for key")
}
}
}
}
To implement, call the addButtonGestureRecognizer function after creating the button, and provide a name for the button (I used the button text) e.g.
addButtonGestureRecognizer(button: keyButton, name: buttonText)
The button name is stored in the "allNames" string array so that it can be matched later in "longPressOfButton".
When the button name is matched in the "longPressOfButton" response function, it passes it to "addKeyFunction" where it is processed.
In my app, I added a toggleSidebar item to the NSToolbar.
#if targetEnvironment(macCatalyst)
extension SceneDelegate: NSToolbarDelegate {
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [NSToolbarItem.Identifier.toggleSidebar, NSToolbarItem.Identifier.flexibleSpace, AddRestaurantButtonToolbarIdentifier]
}
}
#endif
However, when I compile my app to Catalyst, the button is disabled. Does anybody know what else I need to do to hook it up?
If you look at the documentation for .toggleSidebar/NSToolbarToggleSidebarItemIdentifier you will see:
The standard toolbar item identifier for a sidebar. It sends toggleSidebar: to firstResponder.
Adding that method to your view controller will enable the button in the toolbar:
Swift:
#objc func toggleSidebar(_ sender: Any) {
}
Objective-C:
- (void)toggleSidebar:(id)sender {
}
Your implementation will need to do whatever you want to do when the user taps the button in the toolbar.
Normally, under a real macOS app using an NSSplitViewController, this method is handled automatically by the split view controller and you don't need to add your own implementation of toggleSidebar:.
The target needs changed to self, this is shown in this Apple sample where it is done for the print item but can easily be changed to the toggle split item as I did after the comment.
/** This is an optional delegate function, called when a new item is about to be added to the toolbar.
This is a good spot to set up initial state information for toolbar items, particularly items
that you don't directly control yourself (like with NSToolbarPrintItemIdentifier).
The notification's object is the toolbar, and the "item" key in the userInfo is the toolbar item
being added.
*/
func toolbarWillAddItem(_ notification: Notification) {
let userInfo = notification.userInfo!
if let addedItem = userInfo["item"] as? NSToolbarItem {
let itemIdentifier = addedItem.itemIdentifier
if itemIdentifier == .print {
addedItem.toolTip = NSLocalizedString("print string", comment: "")
addedItem.target = self
}
// added code
else if itemIdentifier == .toggleSidebar {
addedItem.target = self
}
}
}
And then add the action to the scene delegate by adding the Swift equivalent of this:
- (IBAction)toggleSidebar:(id)sender{
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
[UIView animateWithDuration:0.2 animations:^{
splitViewController.preferredDisplayMode = (splitViewController.preferredDisplayMode != UISplitViewControllerDisplayModePrimaryHidden ? UISplitViewControllerDisplayModePrimaryHidden : UISplitViewControllerDisplayModeAllVisible);
}];
}
When configuring your UISplitViewController, set the primaryBackgroundStyle to .sidebar
let splitVC: UISplitViewController = //your application's split view controller
splitVC.primaryBackgroundStyle = .sidebar
This will enable your NSToolbarItem with the system identifier .toggleSidebar and it will work automatically with the UISplitViewController in Mac Catalyst without setting any target / action code.
This answer is mainly converting #malhal's answer to the latest Swift version
You will need to return [.toggleSidebar] in toolbarDefaultItemIdentifiers.
In toolbarWillAddItem you will write the following (just like the previous answer suggested):
func toolbarWillAddItem(_ notification: Notification) {
let userInfo = notification.userInfo!
if let addedItem = userInfo["item"] as? NSToolbarItem {
let itemIdentifier = addedItem.itemIdentifier
if itemIdentifier == .toggleSidebar {
addedItem.target = self
addedItem.action = #selector(toggleSidebar)
}
}
}
Finally, you will add your toggleSidebar method.
#objc func toggleSidebar() {
let splitController = self.window?.rootViewController as? MainSplitController
UIView.animate(withDuration: 0.2) {
splitController?.preferredDisplayMode = (splitController?.preferredDisplayMode != .primaryHidden ? .primaryHidden : .allVisible)
}
}
A few resources that might help:
Integrating a Toolbar and Touch Bar into Your App
Mac Catalyst: Adding a Toolbar
The easiest way to use the toggleSidebar toolbar item is to set primaryBackgroundStyle to .sidebar, as answered by #Craig Scrogie.
That has the side effect of enabling the toolbar item and hiding/showing the sidebar.
If you don't want to use the .sidebar background style, you have to implement toggling the sidebar and validating the toolbar item in methods on a class in your responder chain. I put these in a subclass of UISplitViewController.
#objc func toggleSidebar(_ sender: Any?) {
UIView.animate(withDuration: 0.2, animations: {
self.preferredDisplayMode =
(self.displayMode == .secondaryOnly) ?
.oneBesideSecondary : .secondaryOnly
})
}
#objc func validateToolbarItem(_ item: NSToolbarItem)
-> Bool {
if item.action == #selector(toggleSidebar) {
return true
}
return false
}
I connected UITableViews with Show segue. I want to change some variable of destination tableView whenever I tap the Back button on navigation bar. Let's say I'm not allowed to create a new back button, how can I detect/call the destination tableView when the back button is tapped? Like in prepareforsegue we have segue.destination, is there some thing like "backbutton.destination"?
Thanks!
What you should be doing here (I think) is not trying to find the destination of the back button but passing in the interested object when going forwards.
On you detail view you could have something like...
protocol DetailViewDelegate {
func detailViewDidChange()
}
Then in the detail view controller...
class DetailViewController: UIViewController {
weak var delegate: DetailViewDelegate?
// you could call this from viewWillDisappear or something
func doThisWhenYouWantToUpdateTheTableView() {
delegate?.detailViewDidChange()
}
}
Now in the tableview...
func prepareForSegue(...) {
if let vc = segue.destination as? DetailViewController {
vc.delegate = self
}
}
and...
extension MyTableViewController: DetailViewDelegate {
func detailViewDidChange() {
// respond to the change here.
}
}
I recently saw the code down below. Is it possible to link a bar button item to multiple URLs? i.e. by tapping the button, I can then choose which website I wish to go to at runtime? Or can I only link a bar button to one URL?
override func viewDidLoad() {
super.viewDidLoad()
googleButton.addTarget(self, action: "didTapGoogle", forControlEvents: .TouchUpInside)}
and
#IBAction func didTapGoogle(sender: AnyObject) {
UIApplication.sharedApplication().openURL(NSURL(string:"http://www.google.com")!)}
Use it as sidebar menu.
This library keeps your sidebar menu over viewcontroller and navigation bar.
https://github.com/balram3429/BTSimpleSideMenu
It is in objective c, but you can use it by bridging. there are other side bar menus are also available in swift, just make sure it has to be open over view controller and navigation bar.
Import this 2 in bridging file.
//#import "BTSimpleSideMenuClass.h"
//#import "BTSimpleMenuItemClass.h"
Make object
var objBTSimpleSideMenuClass = BTSimpleSideMenuClass()
Import delegate
class YourClassName: UIViewController, BTSimpleSideMenuDelegate {
}
From ViewDidLoad or ViewWillAppear call this method and pass array with name, image etc
func setupOptionMenu(noOfItems : NSMutableArray)
{
objBTSimpleSideMenuClass.delegate = self
let ary : NSMutableArray = []
for var i = 0; i < noOfItems.count; i++
{
let item = BTSimpleMenuItemClass.init(title: noOfItems[i] as! String, image: nil) { (success, item) -> Void in
self.methodOptionMenuTap1()
//self.methodOptionMenuTap2()
//self.methodOptionMenuTap3()
}
ary.addObject(item)
}
let swiftArray = ary as NSArray
objBTSimpleSideMenuClass = BTSimpleSideMenuClass.init(item: swiftArray as [AnyObject], addToViewController: self)
}
Hope this will help you a lot.
All the best.
I have tableview that I add cells to via an "add button" and a second view controllers. I currently have the following code setup for my tableview and it sorts the cells alphabetically whenever the app loads. My issue is that when I add a new recipe it does not re-sort automatically. What is the best way to do this? Should my reloadData() be somewhere else? Or perhaps put it in the Segue from view controller back to table view?
override func viewDidLoad() {
configureView()
if let savedRecipes = loadRecipes() {
recipes2 += savedRecipes
recipes2 = recipes2.sort({current, next in return current.name < next.name})
recipeList.reloadData()
}
}
You could add reloadData() to the viewWillAppear() or viewDidAppear() methods in your table view controller. That way, whenever you return to the view, it should be showing the most up to date data. For example:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
reloadData()
}
If the recipe is a class on its own you can do it like this:
if let savedRecipes = loadRecipes() {
recipes2 += savedRecipes
recipes2.sortUsingComparator {
let r1 = $0 as! Recipe
let r2 = $1 as! Recipe
if r1.title < r2.title {
return .OrderedAscending
}
if r1.title == r2.title {
return .OrderedSame
}
return .OrderedDescending
}
self.tableView.reloadData()
}
This will sort your recipes in ascending order.
You can use the ModelAssistant framework to show recipe objects in tableview. This is a library to mediate between tableView and model. you can set your sort method at the begin of viewcontroller then each new recipe which you add to your model, the model automatically will be sort and also your tableview will be updated too.