Swift button not working properly? - ios

I have a weird bug where a buttons touchUpInside does not work as it should. I have two buttons using the same code
#IBOutlet weak var previousIBO: UIButton!
#IBOutlet weak var nextIBO: UIButton!
#IBAction func buttonDown(sender: AnyObject) {
nextSingleFire()
timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector:#selector(nextFire), userInfo: nil, repeats: true)
}
#IBAction func buttonUp(sender: AnyObject) {
timer.invalidate()
}
nextIBO.addTarget(self, action:#selector(buttonDown(sender:)), for: .touchDown)
nextIBO.addTarget(self, action:#selector(buttonUp(sender:)), for: [.touchUpInside, .touchUpOutside, .touchDragOutside])
previousIBO.addTarget(self, action:#selector(buttonDown(sender:)), for: .touchDown)
previousIBO.addTarget(self, action:#selector(buttonUp(sender:)), for: [.touchUpInside, .touchUpOutside])
However the previous button only works when I drag after tapping. as opposed to the next button that works simply by tapping. Why am I getting this weird behavior in this one button?
I would like to add.touchDragOutside to my previous button, but I can't because then the button does not work

If I were you, I'd start by removing the #IBAction on your functions.
As far as I can tell, they are useless, since you set the actions via "addTarget". And there's a chance your weird behaviour is caused by a wrong connection between your buttons and functions (select your buttons, and open the "Connections inspector" to check if everything is fine).
One other possibility is that your buttons are overlapping, or some view is blocking your button.
Also, Timers can be tricky, take a look at this post for starting and stoping them: swift invalidate timer doesn't work

Related

How to stop infinite loop in swift during action? (iOS)

I am trying to learn how to use "ON" and "OFF" buttons and the basics of swift in general. Everything is going well but when I try to create an infinite loop of vibration using the "ON" UIButton, the loop keeps reiterating and I can't press the "OFF" button to stop it.
I tried looking up ways to stop it but none of them mention how to apply the code. I am still new and learning how to use swift. I read about "UIViewAnimationOptionAllowUserInteraction" but I don't know how to put it into my code.
#IBOutlet weak var label: UILabel!
#IBAction func onSwitch(_ sender: UIButton) {
label.text = "ON"
vibrate()
}
#IBAction func offSwitch(_ sender: UIButton) {
label.text = "OFF"
vibrate()
}
func vibrate() {
while label.text == "ON" {
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
}
}
Your code is continuously re-running tasks in while loop. This is happening on a main thread, so your application is unable to catch off button tap, because every tick is consumed to re-run the AudioServicesPlayAlertSound().
The While Loop is wrong choice here !
You only vibrate is once by replacing the While With if condition to test your code correctly
OR
you can use Timer to vibrate every 3-5 Seconds
let timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.vibrate), userInfo: nil, repeats: true)
// must be internal or public.
#objc func vibrate() {
// Something cool
}

Modifying keyboard toolbar / accessory view with WKWebView

I'm using a WKWebView with a content-editable div as the core of a rich-text editor, and would like to modify the top toolbar above the keyboard—the one that contains the autocorrect suggestions and formatting buttons. (Not sure if this counts as an input accessory view or not).
I've found a few posts showing how to remove the bar, but none of them seems to work, and ideally I'd like to keep the autocorrect part anyway.
At least one app, Ulysses, does this (though I don't know if it's with a web view):
And indeed, I'm pretty sure I can achieve it by doing surgery on the keyboard view hierarchy...but that seems like a tedious and brittle approach.
Is there a better way?
Thanks!
Maybe this tutorial on UITextInputAssistantItem would be helpful.
That said, after fiddling around with this for a while, using WKWebView I still could only get this to work for the first time the keyboard displayed, and every time after that it would return to its original state. The only thing I found that consistently worked was something like the following:
class ViewController: UIViewController {
#IBOutlet weak private var webView: WKWebView!
private var contentView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
webView.loadHTMLString("<html><body><div contenteditable='true'></div></body></html>", baseURL: nil)
for subview in webView.scrollView.subviews {
if subview.classForCoder.description() == "WKContentView" {
contentView = subview
}
}
inputAssistantItem.leadingBarButtonGroups = [UIBarButtonItemGroup(barButtonItems: [UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed(_:)))], representativeItem: nil)]
inputAssistantItem.trailingBarButtonGroups = [UIBarButtonItemGroup(barButtonItems: [UIBarButtonItem(barButtonSystemItem: .camera, target: self, action: #selector(openCamera(_:))), UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(actionPressed(_:)))], representativeItem: nil)]
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow), name: .UIKeyboardDidShow, object: nil)
}
#objc private func donePressed(_ sender: UIBarButtonItem) {
view.endEditing(true)
}
#objc private func openCamera(_ sender: UIBarButtonItem) {
print("camera pressed")
}
#objc private func actionPressed(_ sender: UIBarButtonItem) {
print("action pressed")
}
#objc private func keyboardDidShow() {
contentView?.inputAssistantItem.leadingBarButtonGroups = [UIBarButtonItemGroup(barButtonItems: [UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed(_:)))], representativeItem: nil)]
contentView?.inputAssistantItem.trailingBarButtonGroups = [UIBarButtonItemGroup(barButtonItems: [UIBarButtonItem(barButtonSystemItem: .camera, target: self, action: #selector(openCamera(_:))), UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(actionPressed(_:)))], representativeItem: nil)]
}
}
It will give something like this:
It's a bit frustrating to have to set this on the content view every time the keyboard shows, as well as on the view controller itself, and I hope there's a better way to do this.... But unfortunately I could not find it.
Perhaps a more stable (future-proof) way of having the same behavior would be to place the input accessory view in the WKWebView.superview, then use respond to keyboard events, to show and hide it. It's the old-fashioned way of making input accessory views.
Details:
observe keyboard show/hide notifications
place the input accessory view in the WKWebView.superview
on keyboard show: position the input accessory view above the keyboard
on keyboard hide: hide it with keyboard
And perhaps obvious, use delegation to pass action events between the WKWebView and your UIViewController/UIView subclass.
Also, in iOS 13+, you can override the inputAccessoryView. You should be able to replace it to provide your own on iPad. More details here: https://stackoverflow.com/a/58001395/344591

In Xcode using Swift, how can one make a press on, release off, button?

How can one program a button that executes a set of commands when it is pressed and then stops the execution when it is released in Xcode? For example: when the button is pressed a light is turned on and when it is released the light turns off.
It is pretty simple using IBActions/Targets:
let btn = UIButton()
btn.addTarget(self, action: #selector(self.on(_:)), for: .touchDown)
btn.addTarget(self, action: #selector(self.off(_:)), for: .touchUpInside)
You could also use storyboards to give the same effect
#IBAction func on(_ sender: UIButton?) -> Void {}
#IBAction func off(_ sender: UIButton?) -> Void {}
Then when connecting your actions, connect on() for your onTouchDown and off() for your onTouchUpInside, etc..

IBAction button stays active

I have 2 buttons each below each other and depending on a function it shows what button is enabled and what button is disabled.
#IBOutlet weak var startBtn: workoutButton!
#IBOutlet weak var restBtn: workoutButton!
#IBAction func startBtnPressed(_ sender: AnyObject) {
startBtn.isHidden = true
startBtn.isEnabled = false
perform(#selector(workoutStartVC.revealRestModeBtn), with: 1, afterDelay: 10)
timeLeft = 0
myTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(workoutStartVC.timerRunning), userInfo: nil, repeats: true)
}
#IBAction func restBtnPressed(_ sender: AnyObject) {
print("rest mode button is pressed and i am showing a overlay right now with data count down")
}
When a click the restBtn it still executes the code in the startBtnPressed. How is this possible? Because when I click startBtnPressed 1 time it should disable the button and hide it. It hides it but I am still able to execute the function. So the timer goes twice as fast.
Thanks for the help!
Kevin.
Open your storyboard, select resetButton and make sure there is only one action attached in "Sent Events" section. Right now you will see both IBActions attached to it.
It should be like this:
You probably have something like this:
Delete the IBAction Connection and reconnect them.
This problem happens when both buttons are hooked to both methods.
This usually happens, if you have copied and pasted a button the storyboard.

how to programmatically fake a touch event to a UIButton?

I'm writing some unit tests and, because of the nature of this particular app, it's important that I get as high up the UI chain as possible. So, what I'd like to do is programmatically trigger a button-press, as if the user had pressed the button in the GUI.
(Yes, yes -- I could just call the IBAction selector but, again, the nature of this particular app makes it important that I fake the actual button press, such that the IBAction be called from the button, itself.)
What's the preferred method of doing this?
It turns out that
[buttonObj sendActionsForControlEvents:UIControlEventTouchUpInside];
got me exactly what I needed, in this case.
EDIT: Don't forget to do this in the main thread, to get results similar to a user-press.
For Swift 3:
buttonObj.sendActions(for: .touchUpInside)
An update to this answer for Swift
buttonObj.sendActionsForControlEvents(.TouchUpInside)
EDIT: Updated for Swift 3
buttonObj.sendActions(for: .touchUpInside)
Swift 3:
self.btn.sendActions(for: .touchUpInside)
If you want to do this kind of testing, you’ll love the UI Automation support in iOS 4. You can write JavaScript to simulate button presses, etc. fairly easily, though the documentation (especially the getting-started part) is a bit sparse.
In this case, UIButton is derived from UIControl. This works for object derived from UIControl.
I wanted to reuse "UIBarButtonItem" action on specific use case. Here, UIBarButtonItem doesn't offer method sendActionsForControlEvents:
But luckily, UIBarButtonItem has properties for target & action.
if(notHappy){
SEL exit = self.navigationItem.rightBarButtonItem.action;
id world = self.navigationItem.rightBarButtonItem.target;
[world performSelector:exit];
}
Here, rightBarButtonItem is of type UIBarButtonItem.
For Xamarin iOS
btnObj.SendActionForControlEvents(UIControlEvent.TouchUpInside);
Reference
Swift 5:
class ViewController: UIViewController {
#IBOutlet weak var theTextfield: UITextField!
#IBOutlet weak var someButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
theTextfield.text = "Pwd"
someButton.sendActions(for: .touchUpInside)
}
#IBAction func someButtonTap(_ sender: UIButton) {
print("button tapped")
}
}
It's handy for people who write Unit Tests without UI Tests ;-)
Swift 5 way to solve it for UIBarButtonItem, which does not have sendAction method like UIButton etc.
extension UIBarButtonItem {
func sendAction() {
guard let myTarget = target else { return }
guard let myAction = action else { return }
let control: UIControl = UIControl()
control.sendAction(myAction, to: myTarget, for: nil)
}
}
And now you can simply:
let action = UIBarButtonItem(title: "title", style: .done, target: self, action: #selector(doSomething))
action.sendAction()
Swift 4:
self .yourButton(self)

Resources