I'm currently tying to familiarise myself with UIKit under Swift and work out the best way of adding UI elements programmatically. However I'm finding that a touch can end way outside the button in which it began yet still register as a TouchUpInside event. The ViewController below is from a single view application and it is straightforward to start a touch on, say, button 17, end it on button 18 and still have buttonAction() declare "Button tapped: 17".
Any idea what I'm missing here?
(Edit: This is under Xcode 6 beta 3 BTW.)
// ViewController.swift
import UIKit
class ViewController: UIViewController {
let scrollView:UIScrollView = UIScrollView()
var GWIDTH:Float = 0.0
var GHEIGHT:Float = 0.0
override func viewDidLoad() {
super.viewDidLoad()
GWIDTH = self.view.bounds.size.width
GHEIGHT = self.view.bounds.size.height
scrollView.frame = CGRectMake(10, 10, GWIDTH-10, GHEIGHT-20)
scrollView.contentSize = CGSize(width:GWIDTH-20, height: 0)
self.view.addSubview(scrollView)
for currentTag in 1...30{
var currentButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
currentButton.frame = CGRectMake(100, scrollView.contentSize.height, 100, 50)
currentButton.backgroundColor = UIColor.greenColor()
currentButton.setTitle("Test Button \(currentTag)", forState: UIControlState.Normal)
currentButton.tag = currentTag
currentButton.addTarget(self, action: "buttonAction:", forControlEvents: UIControlEvents.TouchUpInside)
scrollView.addSubview(currentButton)
scrollView.contentSize = CGSize(width:GWIDTH-20,height:2.0+currentButton.frame.size.height+currentButton.frame.origin.y)
}//next
}// end viewDidLoad()
func buttonAction(sender:UIButton!){
println("Button tapped: \(sender.tag)")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
Okay it's taken a fair bit of digging to get to the bottom of this but a couple of helpful Obj-C links...
UIControlEventTouchDragExit triggers when 100 pixels away from UIButton
How to correctly subclass UIControl?
http://www.bytearray.org/?p=5336 (particularly line 89)
...and it seems that the behaviour is standard due to the touch interface (personally I instinctively find the default zone excessive but I'm sure Apple did its homework) and can be overridden by either sub-classing UIControl or interrogating the position of the control event.
I've opted for the latter and here's an implementation specifically in Swift:
// ViewController.swift
import UIKit
class ViewController: UIViewController {
let buttonCount = 3
override func viewDidLoad() {
super.viewDidLoad()
for currentTag:Int in 1...buttonCount{
var currentButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
currentButton.frame = CGRectMake(50, Float(currentTag*50), 120, 50)
currentButton.backgroundColor = UIColor.greenColor()
currentButton.setTitle("Test Button \(currentTag)", forState: UIControlState.Normal)
currentButton.contentEdgeInsets = UIEdgeInsets(top:3,left:6,bottom:3,right:6)
currentButton.tag = currentTag
currentButton.addTarget(self,
action: "btn_TouchDown:",
forControlEvents: UIControlEvents.TouchDown)
currentButton.addTarget(self,
action: "btn_TouchDragExit:",
forControlEvents: UIControlEvents.TouchDragExit)
currentButton.addTarget(self,
action: "btn_TouchUpInside:event:",
forControlEvents: UIControlEvents.TouchUpInside)
currentButton.sizeToFit()
self.view.addSubview(currentButton)
}//next
}// end viewDidLoad()
func btn_TouchDown(sender:UIButton!){
println("TouchDown event: \(sender.tag)\n")
}
func btn_TouchDragExit(sender:UIButton!){
println("TouchDragExit event: \(sender.tag)\n")
}
func btn_TouchUpInside(sender:UIButton!,event:UIEvent!){
println("TouchUpInside event: \(sender.tag)")
var currentTouch:CGPoint = event.allTouches().anyObject().locationInView(sender)
println( "Point: \(currentTouch.x), \(currentTouch.y)\n" )
if currentTouch.x > sender.frame.width{return}
if currentTouch.x < 0 {return}
if currentTouch.y > sender.frame.height{return}
if currentTouch.y < 0 {return}
println("Event ended within frame!")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
Related
I am trying to add a delete button as a subview in an image. This is my current structure:
-> class DesignViewController: UIViewController
|
-> class Sticker: UIImageView, UIGestureRecognizerDelegate
|
-> UI button inside the Sticker
Inside Sticker class I have :
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let button2 = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button2.backgroundColor = .red
button2.setTitle("Delete", for: .normal)
button2.tag = 23
button2.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.addSubview(button2)
}
#objc func buttonAction(sender: UIButton!) {
print("Button tapped")
}
The buttonAction is not getting called.
When I change self.addSubview(button2) line to :
self.superview?.addSubview(button2)
I can see buttonAction getting called. However I would like to keep the button inside the Sticker view so that when user moves the sticker, the button moves as a subview with it.
Can anyone please help and let me know how I can keep the button inside Sticker view?
By default isUserInteractionEnabled property of UIImageView is set to false. Set it to true and your button will start to respond. You can set it in code as well as in the storyboards.
Also try setting the clipsToBounds property of your imageview to true. It will clip your button if it is going outside of the image bounds. That might be one of the reason that your button is not getting touches.
You should create a protocol delegate for button action. This is code example:
protocol ButtonDelegate: class {
func buttonTapped(button: UIButton)
}
class Sticker: UIImageView {
weak var delegate: ButtonDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(button2)
}
lazy var button2: UIButton = {
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button2.backgroundColor = .red
button2.setTitle("Delete", for: .normal)
button2.tag = 23
button2.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
return button
}()
#objc func buttonAction(sender: UIButton) {
guard let delegate = delegate else { return }
delegate.buttonTapped(button: sender)
}
So, now go to your DesignViewControllerl, add your custom imageview class Sticker. Don't forget to do that "imageView.delegate = self". Then in extension add protocol delegate you've created before. Code example:
class DesignViewController: UIViewController {
private lazy var sticker: Sticker = {
let iv = Sticker(frame: view.bounds)
iv.delegate = self
return iv
}()
override viewDidLoad() {
super.viewDidLoad()
view.addSubiew(sticker)
}
}
extension DesignViewController: ButtonDelegate {
func buttonTapped(button: UIButton) {
// input your action here
}
}
I'm working on a universal keyboard extension. Currently I want to calculate the height of a single button. The keyboard has 4 rows as the normal iOS keyboard. Because the space between the buttons (y distance) is 5 I calculated: (self.view.frame.height-(5*5))/4 5x5 because the distance to the top and the bottom is included. Now, if I run the app, one button isn't around 1/4 of the screen. Instead the button's height is around 2/3 of the keyboard view. I really can't find my mistake. I hope you can help me with this calculation.
My current code:
import UIKit
class KeyboardViewController: UIInputViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
print((self.view.frame.height-25)/4)
addButtons()
}
func addButtons() {
let buttonHeigth = (self.view.frame.height-25)/4
let button = ResizableButton()
button.frame = CGRect(x: CGFloat(5), y: CGFloat(10), width: CGFloat(button.frame.size.width), height: CGFloat(buttonHeigth))
button.addTarget(self, action: #selector(keyPressed(sender:)), for: .touchUpInside)
button.layer.cornerRadius = 10
button.layer.masksToBounds = false
self.scrollView.addSubview(button)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated
}
override func textWillChange(_ textInput: UITextInput?) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(_ textInput: UITextInput?) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
let proxy = self.textDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.dark {
textColor = UIColor.white
} else {
textColor = UIColor.black
}
self.nextKeyboardButton.setTitleColor(textColor, for: [])
}
}
I'm trying to have a custom UIButton become hidden once it's pressed a certain number of times...but I'm at a loss.
Having exhausted my limited knowledge and consulting the Apple's documentation as well as the internet for the better part of 3 hours, I've finally made my way here. I've been learning Swift for a short while now and am making an effort to become more familiar with it. This is my first object-oriented language and it's testing me to say the least. Any help with this more likely than not ridiculously simple problem is very much appreciated.
import UIKit
class ViewController: UIViewController{
#IBOutlet weak var buttonMessageDisplay: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
buttonPressed()
}
var tapcount = 0
let buttonMessage : [String] = [/* long array of strings */]
func buttonPressed() {
let button = UIButton(type:.Custom) as UIButton
button.frame = CGRectMake(0, 0, 100, 100)
button.center = CGPointMake(self.view.frame.size.width/2, self.view.frame.size.height/2);
button.backgroundColor = UIColor.redColor()
button.layer.borderColor = UIColor.blackColor().CGColor
button.layer.borderWidth = 3
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.setTitle("", forState: UIControlState.Normal)
button.addTarget(self, action: "buttonPressed", forControlEvents: .TouchUpInside)
view.addSubview(button)
switch tapcount {
case 19...23:
//Hides the button
button.hidden = true
buttonMessageDisplay.text = buttonMessage[tapcount]
case 24...31:
//Unhides the button
button.hidden = false
buttonMessageDisplay.text = buttonMessage[tapcount]
default:
buttonMessageDisplay.text = buttonMessage[tapcount]
}
print("Tap Count: \(tapcount)")
++tapcount
}
Updated with Gesture Recognizer:
import UIKit
class ViewController: UIViewController{
#IBOutlet weak var buttonMessageDisplay: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
buttonMessageDisplay.text = ""
let button = UIButton(type:.Custom) as UIButton
button.frame = CGRectMake(0, 0, 100, 100)
button.center = CGPointMake(self.view.frame.size.width/2, self.view.frame.size.height/2);
button.backgroundColor = UIColor.redColor()
button.layer.borderColor = UIColor.blackColor().CGColor
button.layer.borderWidth = 3
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.setTitle("", forState: UIControlState.Normal)
button.addTarget(self, action: "buttonPressed", forControlEvents: .TouchUpInside)
self.view.addSubview(button)
}
var tapcount : Int = 0
let buttonMessage : [String] = [/* array of strings */]
#IBAction func userTap(sender: UITapGestureRecognizer) {
print("Tap Received")
if case 19...23 = tapcount {
buttonPressed()
}
}
func buttonPressed() {
switch tapcount {
case 0...18:
buttonMessageDisplay.text = buttonMessage[tapcount]
case 19...23:
//Hides the button
button.hidden = true
buttonMessageDisplay.text = buttonMessage[tapcount]
case 24...32:
//Unhides the button
button.hidden = false
buttonMessageDisplay.text = buttonMessage[tapcount]
case 33...100:
buttonMessageDisplay.text = buttonMessage[tapcount]
default:
print("There are no more messages or an error has been encountered")
}
print("Tap Count: \(tapcount)")
++tapcount
}
}
Your code makes no sense. As #formal says in his answer, you're creating a new button on every tap, which is wrong.
You want to define your button in your Storyboard.
Then you want an IBAction method, which takes the button as a parameter:
#IBAction func buttonPressed(sender: UIButton)
{
++tapcount
if tapcount < 19
{
sender.hidden = true
}
}
Note that if the button you're hiding is the same one the user is tapping, once it is hidden, you're done. The user can't tap a hidden button, so there's no way to un-hide it. (And thus no point in your switch statement)
Your main issue is that you are creating a new button every time you call button pressed. Create an #IBOutlet for your button and just set its hidden property in butPressed (which can be set as an action of the button).
Something like:
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
#IBOutlet weak var buttonMessageDisplay: UILabel!
var tapcount = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func butPressed(sender: AnyObject) {
switch tapcount {
case 19...23:
//Hides the button
button.hidden = true
case 24...31:
//Unhides the button
button.hidden = false
default: break
}
print("Tap Count: \(tapcount)")
buttonMessageDisplay.text = "Tap: \(tapcount)"
++tapcount
}
}
The method buttonPressed() creates a new button each time it is called. You should define button as a property similar to buttonMessageDisplay and place the code to initialise it within viewDidLoad().
You should give space between range in case condition:
For example:
(IBAction)buttonTapped:(id)sender {
self.count++;
switch (self.count) {
case 5 ... 23 :
self.button.titleLabel.text = #"disable";
self.button.hidden = true;
break;
default:
break;
}
}
I'm using this code to customize UIButtons on my main ViewController and have placed it in it's own separate .swift file to be referenced wherever a UIButton exists. I want to be able to modify the properties of the MyCustomButton from within my main ViewController after a button is pressed.
For example, I press a button that's using the properties from MyCustomButton and its color changes.
I'm just getting into swift and am not greatly familiar with it's ins and outs quite yet. Any help is appreciated.
import Foundation
import UIKit
class MyCustomButton: UIButton {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.cornerRadius = 25;
self.layer.borderColor = UIColor.blackColor().CGColor
self.layer.borderWidth = 3
self.backgroundColor = UIColor.redColor()
self.tintColor = UIColor.whiteColor()
self.setTitle("", forState: UIControlState.Normal)
self.titleLabel?.numberOfLines = 0
self.titleLabel?.textAlignment = NSTextAlignment.Center
}
}
You can try to override layoutSubviews method:
override func layoutSubviews() {
super.layoutSubviews()
if self.state == UIControlState.Highlighted {
// DO WHAT YOU WANT
} else {
// RESET TO NORMAL
}
}
KVO or delegate protocol (Swift) would work! Either of these would allow you to manipulate the properties of your UI elements when a specific action occurs.
If you want to create new class and change button default behaivour, you should override functions like this one
override func pressesBegan(presses: Set<UIPress>, withEvent event: UIPressesEvent?) {
super.pressesBegan(presses, withEvent: event)
// add your implementation here
}
// func pressesCancelled
// func pressesChanged
// func pressesEnded
// etc.
But if you just want to change button properties on button tap, you can do it on button callback method:
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: UIButtonType.System) as UIButton
button.frame = CGRectMake(100, 100, 100, 100)
button.setTitle("My Button", forState: UIControlState.Normal)
button.addTarget(self, action: "someAction:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(button)
}
func someAction(sender: UIButton) {
print("My Button tapped")
sender.setTitle("Now Tapped", forState: UIControlState.Normal)
}
The "go button" is not working as it should. When clicked it should hide the subviews. Here's the ViewController. I tried placing the final function in various places.
class timeViewController: UIViewController {
var score = 0
var buttonState: Int = 0;
var gameOver = UIView()
#IBAction func start(sender: AnyObject) {
if(displayTime.text != stoptimer.text){
var gameOver = UIView(frame: CGRectMake(0, 0, 0, 0))
gameOver.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.2)
//gameOver.opaque = false
self.view.addSubview(gameOver)
UIView.animateWithDuration(0, animations:{
gameOver.frame.size = CGSizeMake(667, 667)
})
let gobutton = UIButton()
let image = "button.png"
gobutton.setImage(UIImage(named: image), forState: .Normal)
gobutton.frame = CGRectMake(135, 300, 95, 95)
gobutton.addTarget(self, action: "pressed:" , forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(gobutton)
func pressed(sender: UIButton!){
gameOver.removeFromSuperview()
}
}
}
For removing your "GaneOver" view change to this code:
gameOver.removeFromSuperview()
And I hope you are writing "func pressed" outside of viewDidLoad or viewWillAppear
You will have to make gameOver a global variable by declaring it outside function and then do what you did like this:
class myClass {
var gameOver = UIView()
func myFunction() {/*edit everything including gameOverView's properties*/}
func pressed(sender: UIButton!) {
gameOver.removeFromSuperview()
}
}
Also make sure to write functions outside other functions like viewDidLoad but inside your class as it looks like you created that function inside another one. However you can call a function from within another one using something like myFunction() as separate line as is.
Hope that helps :)
You're re-declaring gameOver inside the if statement. Just remove var. Check this code out:
class ViewController: UIViewController {
var gameOver: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func start(sender: AnyObject) {
if(displayTime.text != stoptimer.text){
gameOver = UIView(frame: CGRectMake(0, 0, 0, 0))
gameOver.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.2)
//gameOver.opaque = false
self.view.addSubview(gameOver)
UIView.animateWithDuration(0, animations:{
self.gameOver.frame.size = CGSizeMake(667, 667)
})
let gobutton = UIButton()
let image = "button.png"
gobutton.setImage(UIImage(named: image), forState: .Normal)
gobutton.frame = CGRectMake(135, 300, 95, 95)
gobutton.addTarget(self, action: "pressed:" , forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(gobutton)
}
}
func pressed(sender: UIButton!){
self.gameOver.removeFromSuperview()
}
}