We all know how to make a simple tap on a bar button item present a menu (introduced on iOS 14):
let act = UIAction(title: "Howdy") { act in
print("Howdy")
}
let menu = UIMenu(title: "", children: [act])
self.bbi.menu = menu // self.bbi is the bar button item
So far, so good. But presenting the menu isn't the only thing I want to do when the bar button item is tapped. As long as the menu is showing, I need to pause my game timers, and so on. So I need to get an event telling me that the button has been tapped.
I don't want this tap event to be different from the producing of the menu; for example, I don't want to attach a target and action to my button, because if I do that, then the menu production is a different thing that happens only when the user long presses on the button. I want the menu to appear on the tap, and receive an event telling me that this is happening.
This must be a common issue, so how are people solving it?
The only way I could find was to use UIDeferredMenuElement to perform something on a tap of the menu. However, the problem is that you have to recreate the entire menu and assign it to the bar button item again inside the deferred menu element's elementProvider block in order to get future tap events, as you can see in this toy example here:
class YourViewController: UIViewController {
func menu(for barButtonItem: UIBarButtonItem) -> UIMenu {
UIMenu(title: "Some Menu", children: [UIDeferredMenuElement { [weak self, weak barButtonItem] completion in
guard let self = self, let barButtonItem = barButtonItem else { return }
print("Menu shown - pause your game timers and such here")
// Create your menu's real items here:
let realMenuElements = [UIAction(title: "Some Action") { _ in
print("Menu action fired")
}]
// Hand your real menu elements back to the deferred menu element
completion(realMenuElements)
// Recreate the menu. This is necessary in order to get this block to
// fire again on future taps of the bar button item.
barButtonItem.menu = self.menu(for: barButtonItem)
}])
}
override func viewDidLoad() {
super.viewDidLoad()
let someBarButtonItem = UIBarButtonItem(systemItem: .done)
someBarButtonItem.menu = menu(for: someBarButtonItem)
navigationItem.rightBarButtonItem = someBarButtonItem
}
}
Also, it looks like starting in iOS 15 there's a class method on UIDeferredMenuElement called uncached(_:) that creates a deferred menu element that fires its elementProvider block every time the bar button item is tapped instead of just the first time, which would mean you would not have to recreate the menu as in the example above.
Related
Is this any Default Property in updated iOS14 or is it made in SwiftUI or is it a custom UIView?
If it is Default Property, then how to use it ? (is it available in
Apple Documentation)
If it is in SwiftUI , How to use this property in Swift5 ?
If it is a Custom UIView, then How can I
create one with opening and closing animation effects (if you have
iPhone running iOS 14+, then you can see the animation effects when
it opens or closes , it's like a Scaling Animation I guess) ???
Thanks
There is a new feature in iOS14 (UIKit and swiftUI) called pulldown menu or context menu. Menu can now added to UIButtons and UIBarbuttonItems.
let tbMenu = UIMenu(title: "", children: /* UIActions */)
UIBarButtonItem(image: UIImage(systemName: "list.number"), menu: buttonMenu)
Pull-down menus
Context menus (for TableViews)
There is a very good cocoa pod for this Dropdown
Its working is also very simple
creating dropdown
let dropDown = DropDown()
// The view to which the drop down will appear on
dropDown.anchorView = view // UIView or UIBarButtonItem
// The list of items to display. Can be changed dynamically
dropDown.dataSource = ["Car", "Motorcycle", "Truck"]
Optional Action properties
// Action triggered on selection
dropDown.selectionAction = { [unowned self] (index: Int, item: String) in
print("Selected item: \(item) at index: \(index)")
}
// Will set a custom width instead of the anchor view width
dropDownLeft.width = 200
Display Actions are
dropDown.show()
dropDown.hide()
You can also do very advance things like customise cell , display direction etc have a look at the Documentation
I can't seem to find any information when it comes to animation testing on XCUITest.
The scenario that I am testing is that:
When a button is pressed
Then animation will be displayed on the icon
How do I do this?
I recommend setting a meaningful accessibilityValue on the button when it it's animating, which will let voice over users that something is happening too -- then you can check the value property of the corresponding XCUIElement in your test.
// product code
#IBAction func buttonPressed(_: UIButton) {
self.button.accessibilityIdentifier = "MyButton"
self.button.accessibilityValue = "is animating"
// start animating your button
}
// Test code
let button = self.app.buttons["MyButton"]
button.tap()
XCTAssertEqual(button.value as! String, "is animating")
I'm creating a simple game with a pause button. I want to show another view controller with the pause screen without resetting the game view controller when i return. Every thing i have tried, has resulted in the game view controller being reset, when i press the resume button and return to the game.
Does anybody know how i can do this without resetting the game?
It's a sprite kit and swift iOS app in xcode.
You can show a pop-up based view. You can create .xib and design your pause view as you wish. Then you can resume the game without resetting anything.
Sample code:
// Set Pause Page
var pauseView = PauseView.loadFromNib() // need an extension
#IBAction func Pause(sender: UIButton) {
pauseView.btnResume.addTarget(self, action: #selector(resume(_:)), forControlEvents: .TouchUpInside)
// set place on view
view.addSubview(pauseView)
}
func resume(sender:UIButton) {
pauseView.removeFromSuperview()
}
I created a demo for you:
Full demo available on Github: Get Source Code
Create a variable in your original view controller:
var shouldReset = true
Then, when you perform a segue back, add this in your prepareForSegue:
let vc = destination as! //Your view controller
vc.shouldReset = false
In your viewDidLoad (or wherever you reset your data):
if shouldReset == true {
//Reset
}
I'm three days new into swift development, even Xcode for that matter. I've created a UIView called EncounterMenu that appears over my app when i click the menu button. My goal is to have the EncounterMenu UIView close when I do anything outside of it.. This is what I have so far
override func viewDidLoad() {
super.viewDidLoad()
//create a button that spans the width & height of the encounter menu
let btnEncounterMenu = UIButton(type: UIButtonType.System) as UIButton
btnEncounterMenu.frame = CGRectMake(0, 0, EncounterMenu.frame.width, EncounterMenu.frame.height)
//(this is whats not working) If user clicks outside of button, call function "closeEncounter:"
btnEncounterMenu.addTarget(self, action: "closeEncounter:", forControlEvents: .TouchUpOutside)
//Add the button to the EncounterMenu
EncounterMenu.addSubview(btnEncounterMenu)
}
func closeEncounter(sender: UIButton!) {
EncounterMenu.hidden = true;
}
I tried changing it to TouchUpInside and it worked when i clicked inside the EncounterMenu UIView, so I figured it should be as easy as TouchUpOutside?
Any direction to what I'm doing wrong or how I can accomplish what I'm trying to do?
Touch up outside will not work because you need to be pressing on the menu first, then drag your finger outside for that to register. instead, the easiest option for you to do is to create a button that is the entire size of your view, and on that buttons touch down event, fire off close menu. just make sure that this big button is at the very back of the view, and everything else is built on top of it.
I am creating an app, and to access the main screen of the app the user has to input there fingerprint. I have it setup so that when the fingerprint is correct it programmatically performs a segue to a navigation controller which is connected to the main view controller. Here is my code:
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "Authenticate with Touch ID"
context.evaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reason, reply:
{(succes: Bool, error: NSError!) in
if succes {
self.showOrHide(true)
ProgressHUD.showSuccess("Success")
self.performSegueWithIdentifier("passwordCorrectSegue", sender: nil)
} else {
}
})
} else {
self.touchIDLabel.hidden = true
self.touchIDImage.hidden = true
}
The problem is when I perform the segue and it goes to the navigation controller which shows the view controller, the UIBarButtonItem's do not show on the top left and top right of the screen. You can still click on the top left and top right of the screen and the actions for those buttons would run. The problem is that the UIBarButtonItem's are just not showing. Another thing I have tried is that you also have the option to enter in a password, and when the password is correct it goes to the next view controller... and it works perfectly. Does anyone know how to fix this?
The UIBarButtonItems just don't show when I use the method
self.performSegueWithIdentifier("passwordCorrectSegue", sender: nil)
when trying to perform that method using the fingerprint method.
I had the exact same issue: I have 2 VC, each being able to segue to a third one ; if I segue from the first, the right bar button item is not visible (but still works), but if I segue from the second one, the right bar button item is visible.
I guess it's a bug in iOS9.
The workaround I used was to force the initialization of the right bar button item in the destination view controller "viewDidLoad":
override func viewDidLoad() {
super.viewDidLoad()
// Force right bar button item when using performSeguewithIdentifier (bug in iOS9?)
navigationItem.rightBarButtonItem = UIBarButtonItem(
image: UIImage(assetIdentifier: .Profile),
style: UIBarButtonItemStyle.Plain,
target: self,
action: Selector("displayUser"))
}
That fixed the issue for me (at least until Apple fixes this bug).