I am trying to add default implementations to UIViewController touches began to all controllers conforming to a protocol through a protocol extension. There the touch would be sent to a custom view all controllers implementing this protocol have.
Here's the initial state:
protocol WithView {
var insideView: UIView! { get }
}
class Controller1: UIViewController, WithView {
var insideView: UIView!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
insideView.touchesBegan(touches, with: event)
}
/* Functionality of Controller 1 */
}
class Controller2: UIViewController, WithView {
var insideView: UIView!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
insideView.touchesBegan(touches, with: event)
}
/* Functionality of Controller 2 */
}
What I'd like to accomplish is a situation where all the UIViewControllers forwarded the touches to the insideView without specifying so for every controller the same way. Something like this:
protocol WithView {
var insideView: UIView! { get }
}
extension UIViewController where Self: WithView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
insideView.touchesBegan(touches, with: event)
}
}
class Controller1: UIViewController, WithView {
var insideView: UIView!
/* Functionality of Controller 1 */
}
class Controller2: UIViewController, WithView {
var insideView: UIView!
/* Functionality of Controller 2 */
}
But this does not compile, saying 'Trailing where clause for extension of non-generic type UIViewController'
I tried to define it the other way around, like so:
extension WithView where Self: UIViewController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
insideView.touchesBegan(touches, with: event)
}
}
and while the extension is properly formatted, the compiler complains, as it cannot 'override' things in a protocol extension.
What I'd like is a class extension constrained to a protocol, such as I can override this methods and not being forced to copy-paste code inside all my controllers implementing this protocol.
Edit: as per proposed solutions
I also came up with this solution:
protocol WithView {
var insideView: UIView! { get }
}
extension UIViewController {
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let viewSelf = (self as? WithView) else {
super.touchesBegan(touches, with: event)
return
}
viewSelf.insideView.touchesBegan(touches, with: event)
}
}
class Controller1: UIViewController, WithView {
var insideView: UIView!
/* Functionality of Controller 1 */
}
class Controller2: UIViewController, WithView {
var insideView: UIView!
/* Functionality of Controller 2 */
}
It does what I want, but it feels a bit messy though, because then all the UIViewControllers would intherit this behavior, and would override its code, checking if they implement the protocol.
You can define your own superclass for all view controllers and check if self conforms to the particular protocol (WithView in your case) to decide if you should forward touch events to any other view.
class MyViewController: UIViewController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let selfWithView = self as? WithView {
selfWithView.insideView.touchesBegan(touches, with: event)
} else {
super.touchesBegan(touches, with: event)
}
}
}
This is more flexible approach, you don't have to store insideView property in every view controller subclass.
You could do this by creating a class and sub-classing from it:
class WithViewController: UIViewController, WithView {
var insideView: UIView!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
insideView.touchesBegan(touches, with: event)
}
}
class ViewController: WithViewController {
}
The only downside to this is you have to have a default insideView and it never get's changed.
Related
I'd like to do a label which is present only when button is touched.
When function hidden() is called in MainViewController It's working well but when i'm calling it from ButtonAction class (same function) I get an classic error:
Unexpectedly found nil while unwrapping an Optional value
Here's the code:
// MainViewController.swift
import UIKit
class MainViewController: UIViewController {
#IBOutlet weak var labelToShow: UILabel!
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
labelToShow.isHidden = true
}
func hidden() {
labelToShow.isHidden = true
}
func inHidden() {
labelToShow.isHidden = false
}
}
AND :
// ButtonAction.swift
import UIKit
class ButtonAction: UIButton {
var touched:Bool = false
var mainScreen = MainViewController()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
sleep(1)
mainScreen.hidden()
touched = true
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
mainScreen.inHidden()
touched = false
}
}
The mainScreen instance created with the default initializer MainViewController() is different from those designed in Interface Builder.
You need the actual reference to the main view controller (via IBOutlet or protocol / delegate etc.)
In that line of code you are creating a new instance of your MainViewController :
var mainScreen = MainViewController()
You have to get the reference of your MainViewController() instance (created automatically by storyboard)
I have a UIViewController, a Custum UIView, and a GameController. The CustumUiView is layered on top of the UIViewController and I need to seperate out the views to track clicks on the UIView. I have a custom button in the Custum UIView "Interface View" that is registering the click, but the receiver in the GameController or the UIViewController (tried both) are not registering the clicks.
Code follows and help is appreciated.
Interface View
class InterfaceView: UIView {
var resetButton: UIButton!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
resetButton = UIButton(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
resetButton.setImage(UIImage(named: "reset"), for: UIControlState())
addSubview(resetButton)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// let touches through and only catch the ones on buttons
let hitView = super.hitTest(point, with: event)
if hitView is UIButton {
print("Captured Hit") // Stack Overflow Note: This is working.
return hitView
}
return nil
}
}
Game Controller:
class GameController
{
var interface: InterfaceView!
{
didSet
{
interface.resetButton.addTarget(self, action: #selector (GameController.reset), for: .touchUpInside)
}
#objc func reset(sender: UIButton!)
{
print("button pressed")
}
}
UIViewController:
class GameViewController: UIViewController
{
fileprivate let controller: GameController
#IBOutlet weak var GameView: UIView!
init(_ coder: NSCoder? = nil)
{
// Logic Goes Here
controller = GameController()
if let coder = coder
{
super.init(coder: coder)!
} else
{
super.init(nibName: nil, bundle: nil)
}
}
required convenience init(coder: NSCoder)
{
self.init(coder)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first as UITouch?
{
let point = touch.preciseLocation(in: GameView)
if (touch.view == GameView)
{
print("Do stuff")
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first
{
let point = touch.preciseLocation(in: GameView)
if (touch.view == GameView)
{
print("Do other stuff")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
{
print("Reset Stuff")
}
Resolved by in GameViewController, using didSet to set the target and selector for the button press and then using a method in GameViewController to call a method within GameController.
#IBOutlet weak var interface: InterfaceView!
{
didSet
{
interface.resetButton.addTarget(self, action: #selector (GameViewController.reset), for: .touchUpInside)
}
}
I am relatively new to Swift and I am following some basic tutorials but I seem to be having a problem with some methods which attempt to allow the user to press return to minimise the keyboard or to click off the keyboard and the keyboard will disappear, I understand why I am receiving these errors but have no clue how to go about fixing it, I feel something may have been changed in the newer version of Swift I am using as he is using an older version than me, could anyone possibly explain how to go about fixing these two errors please? Any help would be greatly appreciated here is my source code: (First error, value of type 'viewController' has no member 'text' and secondly, touchesBegan method does not override any method from its superclass)
import UIKit
class ViewController: UIViewController {
#IBAction func buttonPressed(sender: AnyObject) {
label.text = textArea.text
}
#IBOutlet weak var textArea: UITextField!
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.text.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
self.view.endEditing(true)
}
func textFieldShouldReturn(textField: UITextField!) -> Bool {
textField.resignFirstResponder()
return true
}
}
You have 2 problems here, based on the images you posted:
1) The method touhesBegan you are using is not correct:
Correct one:
func touchesBegan(_ touches: Set<UITouch>, withEvent event: UIEvent?)
Yours:
func touchesBegan(touches: NSSet, withEvent event: UIEvent)
I think you want a delegate for the UITextField, so this one is not corerct: touchesBegan is a method for the UIReponder delegate and not for UITextFieldDelegate.
Here you can find the reference for the UITextFieldDelegate.
2) the variable text doesn't exists in your code. I think you wanted to use textArea instead.
Hope this can help you, happy coding!
In your case change following thing:
instead of :
self.text.delegate = self
change :
self.textArea.delegate = self
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
And for delegate add like this
class ViewController: UIViewController, UITextFieldDelegate {
}
I have a textview that I want to allow the user to edit when they double tap. The double tap works fine when the viewcontroller first loads. After I edit the textview and close the textview then reopens for editing with just a single tap. My code is:
import UIKit
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var textView: UITextView!
let myTaps = UITapGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
textView.gestureRecognizers = nil
myTaps.addTarget(self, action: "handleTaps:")
myTaps.numberOfTapsRequired = 2
myTaps.numberOfTouchesRequired = 1
textView.addGestureRecognizer(myTaps)
}
func handleTaps(recognizer: UITapGestureRecognizer) {
print("Taps")
textView.becomeFirstResponder()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
view.endEditing(true)
super.touchesBegan(touches as Set<UITouch>, withEvent: event)
}
func textViewShouldEndEditing(textView: UITextView) -> Bool {
textView.endEditing(true)
return true
}
}
This question already has answers here:
Overriding method with selector 'touchesBegan:withEvent:' has incompatible type '(NSSet, UIEvent) -> ()'
(9 answers)
Closed 7 years ago.
I've got the class Menu: SKScene, then a func touchesBegan inside of it which gives me the error of:
Method 'touchesBegan(:withEvent:)' with Objective-C selector 'touchesBegan:withEvent:' conflicts with method 'touchesBegan(:withEvent:)' from superclass 'UIResponder' with the same Objective-C selector
Anyway, if I add override in front of the function it says:
Method does not override any method from its superclass.
Any idea? The whole code:
import SpriteKit
class Menu: SKScene {
var title : SKLabelNode?
var start : SKLabelNode?
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
self.title = SKLabelNode(fontNamed: "Chalkduster")
self.start = SKLabelNode(fontNamed: "Chalkduster")
self.title!.name = "title"
self.start!.name = "new"
self.addChild(self.title!)
self.addChild(self.start!)
}
func touchesBegan(touches: NSSet, withEvent even: UIEvent) {
self.menuHelper(touches)
}
func menuHelper(touches: NSSet) {
for touch in touches {
let nodeAtTouch = self.nodeAtPoint(touch.locationInNode(self))
if nodeAtTouch.name == "title" {
print("Title pressed")
}
else if nodeAtTouch.name == "new" {
print("Start pressed")
}
}
}
The signature (from the docs) for this method is...
func touchesBegan(_ touches: Set<UITouch>, withEvent event: UIEvent?)
touches is a Set (not NSSet) of UITouch objects and event is an optional UIEvent.
You also need to override it...
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.menuHelper(touches)
}