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)
}
Related
I have the following hierarchy, the root view of the controller. Added two subviews, a standard UIView and a UIScrollView. The UIView is on top.
What I want to do is to receive touches on that UIView, meaning touchesBegan, touchesMoved... like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Began")
super.touchesBegan(touches, with: event)
next?.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Moved")
super.touchesMoved(touches, with: event)
next?.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Ended")
super.touchesEnded(touches, with: event)
next?.touchesEnded(touches, with: event)
}
But at the same time I want the scroll to work. The UIScrollView that is below, I want that to keep scrolling as usual.
This seems impossible to do. If my view handles touches, then I can't forward them to the UIScrollView. But I want both things: see the raw touches on the top view while the scrollview to work as usual.
How can I accomplish this?
The opposite would also work. Meaning, a scrollview on top that scrolls as usual but I also receive the raw touches (touchesBegan, touchesMoved...) on the view that is underneath.
Following this answer worked for me, though with some slight changes. Do note that for this solution, the UIScrollView is on top of the UIView. Firstly, you need to create a subclass of UIScrollView and override the touch methods.
protocol CustomScrollViewDelegate {
func scrollViewTouchBegan(_ touches: Set<UITouch>, with event: UIEvent?)
func scrollViewTouchMoved(_ touches: Set<UITouch>, with event: UIEvent?)
func scrollViewTouchEnded(_ touches: Set<UITouch>, with event: UIEvent?)
}
class CustomScrollView: UIScrollView {
var customDelegate: CustomScrollViewDelegate?
override func awakeFromNib() {
super.awakeFromNib()
for gesture in self.gestureRecognizers ?? [] {
gesture.cancelsTouchesInView = false
gesture.delaysTouchesBegan = false
gesture.delaysTouchesEnded = false
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
customDelegate?.scrollViewTouchBegan(touches, with: event)
super.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
customDelegate?.scrollViewTouchMoved(touches, with: event)
super.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
customDelegate?.scrollViewTouchEnded(touches, with: event)
super.touchesEnded(touches, with: event)
}
}
Take note of the code in awakeFromNib(). UIScrollView has its own set of gestures. So for each gesture, delaysTouchesBegan and delaysTouchesEnded needs to be false to prevent delays for the touch events.
Finally, just assign the delegate to your ViewController and implement the methods like so.
class ViewController: UIViewController {
#IBOutlet weak var scrollView: CustomScrollView!
#IBOutlet weak var touchView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.customDelegate = self
}
}
extension ViewController: CustomScrollViewDelegate {
func scrollViewTouchBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Check if touch location is within the bounds of the UIView
if let touch = touches.first {
let position = touch.location(in: view)
if touchView.bounds.contains(position) {
print("Began")
}
}
}
func scrollViewTouchMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// Check if touch location is within the bounds of the UIView
if let touch = touches.first {
let position = touch.location(in: view)
if touchView.bounds.contains(position) {
print("Moved")
}
}
}
func scrollViewTouchEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// Check if touch location is within the bounds of the UIView
if let touch = touches.first {
let position = touch.location(in: view)
if touchView.bounds.contains(position) {
print("Ended")
}
}
}
}
I have a stylized UIButton subclass that I want to present the standard AirPlay route selection interface on tap. I know AVRoutePickerView is available but it does not appear to offer any customizations except to configure its tint and active tint color on iOS.
Is there a way to present the route picker using your own button?
It appears this app was able to do just that:
Since the AVRoutePickerView is just a view you could add it to a custom button or add custom views on top of it. This is a very rough example
let frame = CGRect(x: 50, y: 50, width: 100, height: 50)
let routePickerView = AVRoutePickerView(frame: frame)
routePickerView.backgroundColor = UIColor.clear
view.addSubview(routePickerView)
let label = UILabel(frame: routePickerView.bounds)
label.backgroundColor = .black
label.textColor = .white
routePickerView.addSubview(label)
label.text = "Rough"
I spoke hastily with my examples of how you could make it work, button tap pass through is touchy as well as trying to use a gesture recognizer. However I came up with three alternative approaches that with some effort can be used. They all rely on having a view added to the picker as a subview as above. That view should have .isUserInteractionEnabled = true. I tested with a UILabel and just with a plain UIView, it probably needs to be something that doesn't have touch processing so avoid UIControls. Once you have this view in place you can use touchesBegan touchesCancelled and touchesEnded to do any visual adjustments or animation then pass along the touches. As always when fiddling with custom provided controls this may not always work but this is currently working for me and doesn't use private api just workarounds and a bit of luck.
Using a custom UILabel as the subview
class CustomLabel: UILabel {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .init(scaleX: 0.75, y: 0.75)
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .identity
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .identity
super.touchesCancelled(touches, with: event)
}
}
Using a subclass of AVRoutePickerView
class CustomRoutePicker: AVRoutePickerView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .init(scaleX: 0.75, y: 0.75)
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .identity
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .identity
super.touchesCancelled(touches, with: event)
}
}
Using UIViewController with a reference to a pickerView
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
pickerView?.transform = .init(scaleX: 0.75, y: 0.75)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
pickerView?.transform = .identity
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
pickerView?.transform = .identity
}
I would like to make a UIView highlighted after it's pressed and return to the normal color after release. What is the best practise for doing this?
Subclass the UIView:
class CustomView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.blue
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
backgroundColor = UIColor.red
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
backgroundColor = UIColor.blue
}
}
Apple about overriding touchesBegan and touchesEnded:
When creating your own subclasses, call super to forward any events
that you do not handle yourself. If you override this method without
calling super (a common use pattern), you must also override the other
methods for handling touch events [i.e. touchesEnded, touchesMoved, touchesCancelled], even if your implementations do
nothing.
Further reading:
https://developer.apple.com/documentation/uikit/uiview
Proper practice for subclassing UIView?
Very simple example - you can run it in a Playground page:
//: Playground - noun: a place where people can play
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func viewDidLoad() {
view.backgroundColor = .red
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.backgroundColor = .green
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
view.backgroundColor = .red
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
In practice, though, you want some additional code to check state, handle touchesCancelled, etc.
This is just to get you going - read up on touch events at: https://developer.apple.com/documentation/uikit/uiview
I want to change the color of a UIView based on the number of touches currently on it. I have managed to create this with a single touch:
class colorChangerViewClass: UIView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
setColor(color: .blue)
print("touchesBegan")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
setColor(color: .green)
print("touchesEnded")
}
func setColor(color: UIColor) {
self.backgroundColor = color
}
}
But I am struggling with implementing multitouch. I feel like I'm not understanding something here.
Does UIView have a property to check how many fingers are currently touching it?
I could check that each time a new touchesBegan or touchesEnded occurs.
First enable multi touch on your subclass
self.isMultipleTouchEnabled = true
Then inside your touch event functions you can get all touches with event from your UIView class.
let touchCount = event?.touches(for: self)?.count
Make sure the UIView has isMultipleTouchEnabled = true, the default is false.
Then change the touchesBegan method
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
setColor(color: .blue)
let touchCount = event?.touches(for: self)?.count
}
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
}