Im making a subclass of UIButton to have a custom sublayer on the buttons layer.
I make this button a System type in storyboard and just replace class from UIButton my subclass in storyboard under Identity inspector.
I use System type button instead of custom to get that fade animation on titleLabel.
Now, I want to fade the sublayer aswell when the titleLabel fades.
Ive tried the begin/continue/endTrackingWithTouch: methods but it doesnt change when the titleLabel changes.
class TAButton:UIButton {
let innerShadowLayer = CFInnerShadow() // Custom class of CAGradientLayer
override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent) -> Bool {
innerShadowLayer.opacity = 0.5
return super.beginTrackingWithTouch(touch, withEvent: event)
}
override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent) -> Bool {
let con = super.continueTrackingWithTouch(touch, withEvent: event)
println(con) // I expected this to change to false when titleLabel fades but I was wrong, its always true
return con
}
override func endTrackingWithTouch(touch: UITouch, withEvent event: UIEvent) {
innerShadowLayer.opacity = 1.0
super.endTrackingWithTouch(touch, withEvent: event)
}
func setup() {
self.setBackgroundGradient(UIColor(hex: 0x4A9BCB, alpha: 0.25), color2: UIColor(hex: 0xBAE5FF, alpha: 0.25)) // Custom function Im using
self.makeRoundSides() // Custom function Im using
innerShadowLayer.frame = self.bounds
innerShadowLayer.cornerRadius = self.layer.cornerRadius
innerShadowLayer.innerShadowRadius = 10
innerShadowLayer.innerShadowOpacity = 0.6
innerShadowLayer.innerShadowColor = UIColor(hex: 0x69FFF7, alpha: 1.0).CGColor
self.layer.addSublayer(innerShadowLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
}
This seems to work for me if I override touchesBegan and touchesEnded instead.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
innerShadowLayer.opacity = 0.5
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
innerShadowLayer.opacity = 1.0
}
Related
Is it possible for a subclass of a UIButton to trigger a segue in storyboard?
The segue stop working as soon as I change the custom class of a button in storyboard.
I have UIPageViewController embedded in a ViewController inside a NavigationController. One of the ViewControllers in the PageController have a subclassed Button that should perform a segue in the NavigationController.
The Button subclass is as follows:
Every thing else is wired up in storyboard only
class MiniButton:UIButton{
var originalBackgroundColor:UIColor?
var originalTextColor:UIColor?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
originalBackgroundColor = self.backgroundColor
originalTextColor = self.titleColor(for: .normal)
self.backgroundColor = MiniColor.miniYellow
self.setTitleColor(MiniColor.white, for: .normal)
}
func backToOriginalColors(){
self.backgroundColor = originalBackgroundColor
self.setTitleColor(originalTextColor, for: .normal)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
backToOriginalColors()
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
backToOriginalColors()
}
}
Calling super on your subclass' methods is 'super' important =(
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event) // <--------
originalBackgroundColor = self.backgroundColor
originalTextColor = self.titleColor(for: .normal)
self.backgroundColor = MiniColor.miniYellow
self.setTitleColor(MiniColor.white, for: .normal)
}
I have a requirement to move a label around only when the user touches on it and drags. I cant find out whether the user touched that label or not. I have used the touchesMoved method(Swift 2). Below is my code
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches as Set<UITouch>, withEvent: event)
let touch = touches.first
if (touch!.view) == (moveLabel as UIView) // moveButton is my label
{
location = touch!.locationInView(self.view)
moveLabel.center = location
}
}
When I do this, my label is not moving :(
someone help me please
I had been thinking in your question, and this is my result, you need to subclass UILabel and in the init you need to set userInteractionEnabled = true and then override this method override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) well, my code is this:
Swift 3.1 Code
import UIKit
class draggableLabel: UILabel {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.red.cgColor
self.isUserInteractionEnabled = true
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first;
let location = touch?.location(in: self.superview);
if(location != nil)
{
self.frame.origin = CGPoint(x: location!.x-self.frame.size.width/2, y: location!.y-self.frame.size.height/2);
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
}
Swift 2.2 Code
import UIKit
class draggableLabel: UILabel {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.redColor().CGColor
self.userInteractionEnabled = true
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first;
let location = touch?.locationInView(self.superview);
if(location != nil)
{
self.frame.origin = CGPointMake(location!.x-self.frame.size.width/2, location!.y-self.frame.size.height/2);
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
}
Hope this helps you, for me works great, this is how it works
I have 2 GesturerRecognizers, one is a custom 3DTouch and another one is the single UITapGestureRegognizer. I'd like to make that if you tap once it performs a segue and if you go trought the 3dtouch one you get another view. I have tried lot of things but it is not working (i have tried with timer even, but always is 0 at start, no matters if its 3dtouch or not.) That my 3dtouch custom implementation:
//Without this import line, you'll get compiler errors when implementing your touch methods since they aren't part of the UIGestureRecognizer superclass
import UIKit.UIGestureRecognizerSubclass
//Since 3D Touch isn't available before iOS 9, we can use the availability APIs to ensure no one uses this class for earlier versions of the OS.
#available(iOS 9.0, *)
public class ForceTouchGestureRecognizer: UIGestureRecognizer {
var timer = NSTimer()
var counter = Double()
//Since it also doesn't make sense to have our force variable as a settable property, I'm using a private instance variable to make our public force property read-only
private var _force: CGFloat = 0.0
//Because we don't know what the maximum force will always be for a UITouch, the force property here will be normalized to a value between 0.0 and 1.0.
public var force: CGFloat { get { return _force } }
public var maximumForce: CGFloat = 4.0
func timerAction() {
counter += 1
}
override public func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
counter = 0
timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
print("COUNTER: \(counter)")
normalizeForceAndFireEvent(.Began, touches: touches)
}
override public func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent) {
super.touchesMoved(touches, withEvent: event)
normalizeForceAndFireEvent(.Changed, touches: touches)
}
override public func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
print("COUNTER: \(counter)")
normalizeForceAndFireEvent(.Ended, touches: touches)
timer.invalidate()
}
override public func touchesCancelled(touches: Set<UITouch>, withEvent event: UIEvent) {
super.touchesCancelled(touches, withEvent: event)
normalizeForceAndFireEvent(.Cancelled, touches: touches)
}
func normalizeForceAndFireEvent(state: UIGestureRecognizerState, touches: Set<UITouch>) {
//Putting a guard statement here to make sure we don't fire off our target's selector event if a touch doesn't exist to begin with.
guard let firstTouch = touches.first else {
return
}
//Just in case the developer set a maximumForce that is higher than the touch's maximumPossibleForce, I'm setting the maximumForce to the lower of the two values.
maximumForce = min(firstTouch.maximumPossibleForce, maximumForce)
//Now that I have a proper maximumForce, I'm going to use that and normalize it so the developer can use a value between 0.0 and 1.0.
_force = firstTouch.force / maximumForce
//Our properties are now ready for inspection by the developer. By setting the UIGestureRecognizer's state property, the system will automatically send the target the selector message that this recognizer was initialized with.
self.state = state
}
//This function is called automatically by UIGestureRecognizer when our state is set to .Ended. We want to use this function to reset our internal state.
public override func reset() {
super.reset()
_force = 0.0
}
}
And that is what i do in the view:
self.forceTouchRecognizer = ForceTouchGestureRecognizer(target: self, action: #selector(PLView.handleForceTouchGesture(_:)))
self.addGestureRecognizer(forceTouchRecognizer)
self.nowTap = UITapGestureRecognizer(target: self, action: #selector(StoryTableViewController.didTapRightNow2(_:)))
self.addGestureRecognizer(nowTap)
If I understand you correctly you want to add 2 different GestureRecognizers to the same UIView. One that detects a normal tap and one that detects a tap with force.
For the normal tap you can use a UITapGestureRecognizer. For the force tap you have to create your own custom Gesture Recognizer that monitors the force of the touch decides if the force was high enough to qualify for a force tap gesture.
Here is the custom force tap gesture recognizer:
import UIKit.UIGestureRecognizerSubclass
#available(iOS 9.0, *)
public class ForceTapGestureRecognizer: UIGestureRecognizer {
private var forceTapRecognized = false
override public func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
forceTapRecognized = false
}
override public func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent) {
super.touchesMoved(touches, withEvent: event)
guard let touch = touches.first else { return }
if touch.force >= touch.maximumPossibleForce {
forceTapRecognized = true
}
}
override public func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
state = forceTapRecognized ? .Ended : .Failed
}
}
Then you can add both recognizers to your view and add an action for each recognizer.
To make both recognizers work at the same time you have to tell the UITapGestureRecognizer that it should only detect a tap, when the ForceTapGestureRecognizer did not detect a force tap. You do this with requireGestureRecognizerToFail(_:). If you do not set this, only the normal taps will be recognized:
class ViewController: UIViewController {
let touchView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
override func viewDidLoad() {
super.viewDidLoad()
let touchView = UIView()
view.addSubview(touchView)
let forceTapRecognizer = ForceTapGestureRecognizer(target: self, action: #selector(ViewController.didForceTap(_:)))
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.didTap(_:)))
tapRecognizer.requireGestureRecognizerToFail(forceTapRecognizer)
touchView.addGestureRecognizer(forceTapRecognizer)
touchView.addGestureRecognizer(tapRecognizer)
}
func didTap(recognizer: UITapGestureRecognizer) {
print("tap")
}
func didForceTap(recognizer: ForceTapGestureRecognizer) {
print("force tap")
}
}
Basically I need to create a UIScrollView in my SpriteKit project but i'm having a lot of problem adding SKButtons (custom SKNode class for button management). So I proceeded to create a scrollable SKNode with touch gesture but, obviously, this bar won't have the native UIScrollView acceleration: the feature I was looking for.
So tried to overtake this problem by adding a native UIScrollView and catch every change of position, like this:
using this code:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[blueNode setPosition:CGPointMake(blueNode.position.x, scrollerUI.contentOffset)];
}
This works right but foolishly I forgot that if I add buttons, the touch gesture won't recognize the button's touch event! (The UIScrollView has the priority).
Maybe is just a stupid question but I don't really know how to figure it out. Maybe programming my own acceleration methods?
I think I came up with a solution to handle touches. Since the UIScrollView eats all the touches to allow it to scroll you need to pass them to the SKScene. You can do that by subclassing UIScrollView:
class SpriteScrollView: UIScrollView {
var scene: SKScene?
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
scene?.touchesBegan(touches, withEvent: event)
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
scene?.touchesMoved(touches, withEvent: event)
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
scene?.touchesCancelled(touches, withEvent: event)
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
scene?.touchesEnded(touches, withEvent: event)
}
}
Then your scene needs to see if the touch hits any nodes and appropriately send the touches to those nodes. So in your SKScene add this:
var nodesTouched: [AnyObject] = []
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
let location = (touches.first as! UITouch).locationInNode(self)
nodesTouched = nodesAtPoint(location)
for node in nodesTouched as! [SKNode] {
node.touchesBegan(touches, withEvent: event)
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesMoved(touches, withEvent: event)
for node in nodesTouched as! [SKNode] {
node.touchesMoved(touches, withEvent: event)
}
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
for node in nodesTouched as! [SKNode] {
node.touchesCancelled(touches, withEvent: event)
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
for node in nodesTouched as! [SKNode] {
node.touchesEnded(touches, withEvent: event)
}
}
Note this is for single touches and does not handle errors well, you'll need to wrap some of the forced down casting in if statements if you want to do this properly
I hope this helps someone even though the question is months old.
One possible solution would be to add tap gesture to scene:
override func didMoveToView(view: SKView) {
let tap : UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tapped:"))
view.addGestureRecognizer(tap)
}
func tapped(tap : UITapGestureRecognizer) {
let viewLocation = tap.locationInView(tap.view)
let location = convertPointFromView(viewLocation)
let node = nodeAtPoint(location)
if node.name == "lvlButton" {
var button = node as! SKButton
button.touchBegan(location)
}
}
I have strange issue with touchesBegan handler being called more than necessary.
If I tapped the UIView (UIButton) fast 2 times, touchesBegan was called 3 times.
I solved the issue with simple time measurement but I am still interested what would be the reason for this kind of behaviour?
Here is the code (with already added time-checking):
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
if let t:UITouch = touches.anyObject() as? UITouch
{
if !CGRectContainsPoint(CGRectMake(0, 0, self.frame.width, self.frame.height), t.locationInView(self))
{
touchesCancelled(touches, withEvent: event)
}
}
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
ForCancelTouch = false
setupButtonGUI(true)
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
if !ForCancelTouch
{
if abs(LastValidTouchesBeganDate.timeIntervalSinceNow) > DelayBetweenFastTapping
{
NSNotificationCenter.defaultCenter().postNotificationName(SBDCNotificationNameActionSTBMakeOneCommand, object: self, userInfo: ["tag":self.tag])
LastValidTouchesBeganDate = NSDate()
}
}
setupButtonGUI(false)
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
ForCancelTouch = true
setupButtonGUI(false)
}
Well - it seems the issue happens ONLY when clicking/testing with mouse inside iOS simulator! It looks like it is an iOS simulator bug.