I have a 5 segment segmentedControl which I've subclassed using the code from this post:
How to deselect a segment in Segmented control button permanently till its clicked again
to allow the controller to be deselected when the selected segment is touched for a second time.
This works visually but when trying to assign a UserDefault it's just recognised as the segment that was touched twice.
I can't figure out what I can add to either the subclass code, or the viewController code to make this work.
Any help would be appreciated.
SUBCLASS CODE:
class mySegmentedControl: UISegmentedControl {
#IBInspectable var allowReselection: Bool = true
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let previousSelectedSegmentIndex = self.selectedSegmentIndex
super.touchesEnded(touches, with: event)
if allowReselection && previousSelectedSegmentIndex == self.selectedSegmentIndex {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
if bounds.contains(touchLocation) {
self.sendActions(for: .valueChanged)
self.selectedSegmentIndex = UISegmentedControlNoSegment
}
}
}
}
}
VIEWCONTROLLER CODE:
#IBOutlet weak var METDome_L: UISegmentedControl!
let key_METDome_L = "METDome_L"
var METD_L: String!
#IBAction func METDome_Lselect(_ sender: Any) {
if METDome_L.selectedSegmentIndex == 0{
METD_L = "1"
UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
}
else if METDome_L.selectedSegmentIndex == 1{
METD_L = "2"
UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
}
else if METDome_L.selectedSegmentIndex == 2{
METD_L = "3"
UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
}
else if METDome_L.selectedSegmentIndex == 3{
METD_L = "4"
UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
}
else if METDome_L.selectedSegmentIndex == 4{
METD_L = "5"
UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
}
else{
METD_L = "NONE"
UserDefaults.standard.set(METD_L, forKey: key_METDome_L)
}
}
First of all if you are subclassing the control you have to use that type to get the enhanced functionality.
#IBOutlet weak var METDome_L: MySegmentedControl! // class names start with a capital letter
Add a property selectionKey and save the state UISegmentedControlNoSegment (as Int) in UserDefaults when the control is deselected. A fatal error is thrown if the property is empty (it has to be set in the view controllers which use the subclass).
class MySegmentedControl: UISegmentedControl {
#IBInspectable var allowReselection: Bool = true
var selectionKey = ""
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let previousSelectedSegmentIndex = self.selectedSegmentIndex
super.touchesEnded(touches, with: event)
if allowReselection && previousSelectedSegmentIndex == self.selectedSegmentIndex {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
if bounds.contains(touchLocation) {
self.sendActions(for: .valueChanged)
self.selectedSegmentIndex = UISegmentedControlNoSegment
guard !selectionKey.isEmpty else { fatalError("selectionKey must not be empty")
UserDefaults.standard.set(UISegmentedControlNoSegment, forKey: selectionKey)
}
}
}
}
}
In your view controller set the property selectionKey in viewDidLoad to the custom key and save the selected state of the control to UserDefaults in the IBAction. Do not any math. The first segment is segment 0 with index 0. Get used to zero-based indices. That makes it more convenient to restore the selected state.
#IBOutlet weak var METDome_L: MySegmentedControl!
let key_METDome_L = "METDome_L"
override func viewDidLoad()
{
super.viewDidLoad()
METDome_L.selectionKey = key_METDome_L
}
#IBAction func METDome_Lselect(_ sender: UISegmentedControl) {
UserDefaults.standard.set(sender.selectedSegmentIndex, forKey: key_METDome_L)
}
Related
I want my character to jump whenever I press two buttons at the same time. I've already tried this:
if rightButton.contains(location) && leftButton.contains(location) {
character.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 50))
}
One approach would be:
In your functions that detects the interaction with the button prepare it with a boolean.
Then in your Update function, use a timer to add a range of time where we can say that both buttons are pressed at the same time (100 ms for example).
I'll let you here some pseudocode that I hope it helps.
func RightBtnClick()->Void{
rightBtnPressed = true
}
func LeftBtnClick()->Void{
leftBtnPressed = true
}
func Start()->Void{
rightBtnTimer = 0
leftBtnTimer = 0
}
func Update(deltatime ms:float)->Void{
if(rightBtnPressed){
rightBtnTimer += ms;
if(rightBtnTimer>100){
rightBtnTimer = 0
rightBtnPressed=false
}
}
if(leftBtnPressed){
leftBtnTimer += ms;
if(leftBtnTimer>100){
leftBtnTimer = 0
leftBtnPressed=false
}
}
// Lastly let's check if both are pressed.
if(leftBtnPressed && rightBtnPressed){
DoStuff()
}
}
First of all, make sure in GameViewController.swift you have multitouch enabled.
class GameViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
// ...
if let view = self.view as! SKView?
{
// ...
view.isMultipleTouchEnabled = true
}
}
}
In GameScene give name to your buttons. On tap we will create a list of every node your fingers touched that has a name. If the list contains both right and left button, it means he pressed both at the same time.
class GameScene: SKScene
{
override func didMove(to view: SKView)
{
// add name to each button
left_button.name = "left_button"
right_button.name = "right_button"
}
func buttons_touched(_ touches: Set<UITouch>) -> [String]
{
// get the list of buttons we touched on each tap
var button_list : [String] = []
for touch in touches
{
let positionInScene = touch.location(in: self)
let touchedNode = self.nodes(at: positionInScene)
let buttons = touchedNode.compactMap { (node) -> String in
node.name ?? ""
}.filter({$0 != ""})
button_list += buttons
}
return button_list
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let buttons_tapped = buttons_touched(touches)
if buttons_tapped.contains("right_button") && buttons_tapped.contains("left_button")
{
// jump code
}
}
}
You can simulate multitouch inside Simulator by holding Option button.
I'm developing an app where I had to build my own gesture recognition (Apple's were too precise)
I need to use a second view controller (settings), and when I'm on the second one, it still react to the first one.
Where it's very problematic it's that with these gesture I control things in the house (such as lighting, music or even more), so imagine you click on a button on the second view and it light up the room, or you swipe down on the second view and the music is cut...
I'm using a segue to go to the SecondViewController
self.performSegue(withIdentifier: "SecondViewController", sender:self)
I've created another swift file for the second view controller, where, for now, there is nothing.
Do you have any idea what's going on and how I can solve that?
Thanks!
EDIT: As requested, here is more code :
import UIKit
class ViewController: UIViewController {
//Variables definition
override func viewDidLoad() {
super.viewDidLoad()
ascending = false
UIScreen.main.wantsSoftwareDimming = true
UIScreen.main.brightness = (0.0)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.timer.invalidate()
isPinched = false
super.touchesBegan(touches, with: event)
startTime = getCurrentMillis()
for touch in touches{
let point = touch.location(in: self.view)
for (index,finger) in fingers.enumerated() {
if finger == nil {
fingers[index] = String(format: "%p", touch)
if (finger1.isEmpty){
finger1 = [point.x, point.y]
}
//And so on until finger10
break
}
}
}
//If the gesture is long enough, I have a special recognition (longPress)
timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(increaseValue), userInfo: nil, repeats: true)
if ascending {
ascending = false
}
else {
ascending = true
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
for touch in touches {
let point = touch.location(in: self.view)
for (index,finger) in fingers.enumerated() {
if let finger = finger, finger == String(format: "%p", touch) {
switch (index){
case 0 :
finger1 += [point.x, point.y]
//And so on until case 9 / finger10
default :
break
}
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
endTime = getCurrentMillis()
for touch in touches {
for (index,finger) in fingers.enumerated() {
if let finger = finger, finger == String(format: "%p", touch) {
fingers[index] = nil
break
}
}
}
direction[0] = ""
direction[1] = ""
direction[2] = ""
direction[3] = ""
direction[4] = ""
if finger1.count != 0 {
direction[0] = GestureRecognizer(coordinates: finger1, index: 0)
}
if finger2.count != 0 {
direction[1] = GestureRecognizer(coordinates: finger2, index: 1)
}
if finger3.count != 0 {
direction[2] = GestureRecognizer(coordinates: finger3, index: 2)
}
if finger4.count != 0 {
direction[3] = GestureRecognizer(coordinates: finger4, index: 3)
}
if finger5.count != 0 {
direction[4] = GestureRecognizer(coordinates: finger5, index: 4)
}
if Int64(endTime - startTime) < 600 {
//My gesture recognition
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.directionLabel.text = self.labelUpdate
self.finger1 = []
self.finger2 = []
self.finger3 = []
self.finger4 = []
self.finger5 = []
self.finger6 = []
self.finger7 = []
self.finger8 = []
self.finger9 = []
self.finger10 = []
}
}
//One of the function I call
func swipeLeftTwo(){
request(urlAdress: "Url Adress")
}
func swipeRightFour(){
self.performSegue(withIdentifier: "SecondViewController", sender:self)
}
}
In the second view controller:
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var labelTe: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
labelTe.text = "something changed"
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I have this splash view and I'm having problems with use3dtouch because I'm having an error telling me that splashview has no instance member 'use3dtouch'. Here is the code.
Here is an image of the error
The error
import Foundation
import UIKit
class VPSplashView : UIView {
var vp = VPSplashView()
private lazy var __once: () = {
if VPSplashView.traitCollection.forceTouchCapability == UIForceTouchCapability.unavailable
{
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(VPSplashView.longPressed(_:)))
self.addGestureRecognizer(longPressRecognizer)
VPSplashView.use3DTouch = false
} else {
VPSplashView.use3DTouch = true
}
}()
static func addSplashTo(_ view : UIView, menuDelegate: MenuDelegate) -> VPSplashView{
let splashView = VPSplashView(view: view)
splashView.backgroundColor = UIColor.clear
splashView.isExclusiveTouch = true
if (view.isKind(of: UIScrollView.classForCoder())){
(view as! UIScrollView).canCancelContentTouches = false
}
splashView.menu?.delegate = menuDelegate
return splashView
}
// MARK: Initialization
var menu : VPSplashMenu?
fileprivate var use3DTouch : Bool = true
var onceToken: Int = 0
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init(view: UIView){
super.init(frame:view.bounds)
view.addSubview(self)
self.menu = VPSplashMenu.init(center: self.center)
}
func setDataSource(_ source: MenuDataSource!){
self.menu?.dataSource = source
}
override func layoutSubviews() {
super.layoutSubviews()
self.superview?.bringSubview(toFront: self)
if (self.superview != nil){
self.setup()
}
}
fileprivate func setup(){
_ = self.__once;
}
// MARK: Long Press Handling
func longPressed(_ sender: UILongPressGestureRecognizer)
{
switch sender.state {
case .began:
let centerPoint = sender.location(in: self)
menu?.movedTo(centerPoint)
menu?.showAt(self)
menu?.squash()
case .ended:
menu?.cancelTap()
menu?.removeFromSuperview()
case .changed:
let centerPoint = sender.location(in: self)
menu?.handleTap((menu?.convert(centerPoint, from: self))!)
default:
menu?.removeFromSuperview()
}
}
// MARK: Touch Handling
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if (use3DTouch == true){
var centerPoint : CGPoint = CGPoint.zero
for touch in touches {
centerPoint = touch.location(in: self)
menu?.movedTo(centerPoint)
menu?.showAt(self)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if (use3DTouch == true){
for touch in touches {
let centerPoint = touch.location(in: self)
if (menu?.shown == false){
menu?.movedTo(centerPoint)
if (touch.force > minimalForceToSquash){
menu?.squash()
}
} else {
menu?.handleTap((menu?.convert(centerPoint, from: self))!)
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if (use3DTouch == true){
menu?.hide()
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
if (use3DTouch){
menu?.hide()
}
}
}
Your added code has clarified some important things to write an answer...
traitCollection is an instance property which is declared in UITraitEnvironment, where UIView conforms to
use3DTouch is an instance property which is declared in VPSplashView explicitly
longPressed(_:) is an instance method which is declared in VPSplashView explicitly
The third is not critical in this case, but the first two are clearly related to the error message you have gotten: Instance member cannot be used.
When you access to instance members (in this case, instance properties), you need to prefix an instance reference before .memberName, not a class name.
In your case, self seems to be an appropriate instance:
private lazy var __once: () = {
//###
if self.traitCollection.forceTouchCapability == UIForceTouchCapability.unavailable
{
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressed(_:)))
self.addGestureRecognizer(longPressRecognizer)
//###
self.use3DTouch = false
} else {
//###
self.use3DTouch = true
}
}()
But you are doing things in far more complex way than needed, so you may need some more fixes.
One critical thing is this line:
var vp = VPSplashView()
This may cause some runtime issue (even if you have solved some compile-time issue) and vp is not used. Better remove it.
I am new to creating games and such so as a practice I am making a silly game currently. This game has a briefcase which contains buttons (0-9) I would like to save the value of the number that was pressed in an array. I am unsure of how to use touches began to do a specific action if a specific type is pressed.
ButtonInput:
class ButtonInput: SKSpriteNode{
var value: Int = 0
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
}
loadView():
let briefcase = ButtonInput(imageNamed: "briefcase")
let Button0 = ButtonInput(imageNamed: "Button0")
let Button1 = ButtonInput(imageNamed: "Button1")
let Button2 = ButtonInput(imageNamed: "Button2")
let Button3 = ButtonInput(imageNamed: "Button3")
let Button4 = ButtonInput(imageNamed: "Button4")
let Button5 = ButtonInput(imageNamed: "Button5")
let Button6 = ButtonInput(imageNamed: "Button6")
let Button7 = ButtonInput(imageNamed: "Button7")
let Button8 = ButtonInput(imageNamed: "Button8")
let Button9 = ButtonInput(imageNamed: "Button9")
Button0.value = 0
Button1.value = 1
Button2.value = 2
Button3.value = 3
Button4.value = 4
Button5.value = 5
Button6.value = 6
Button7.value = 7
Button8.value = 8
Button9.value = 9
UPDATE:
I tried making a touches began after hours of research and I came up with this:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject! in touches {
let touchLocation = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
let name = touchedNode.name
if name == "Button0"
{
userSequence.append(Button0.value)
print("Button0 pressed")
}
if name == "Button1"
{
userSequence.append(Button1.getValue())
print("Button1 pressed")
}
if name == "Button2"
{
userSequence.append(Button2.value)
}
if name == "Button3"
{
userSequence.append(Button3.value)
}
if name == "Button4"
{
userSequence.append(Button4.value)
}
if name == "Button5"
{
userSequence.append(Button5.value)
}
if name == "Button6"
{
userSequence.append(Button6.value)
}
if name == "Button7"
{
userSequence.append(Button7.value)
}
if name == "Button8"
{
userSequence.append(Button8.value)
}
if name == "Button9"
{
userSequence.append(Button9.value)
}
if name == "ButtonEnter"
{
compareSequences()
}
}
}
If you use SpriteKit, I would do something like this:
import SpriteKit
var _button = [SKSpriteNode]()
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if(self.nodeAtPoint(location).name != nil && self.nodeAtPoint().name != "enter") {
_array.append(self.nodeAtPoint(location).name)
}
}
}
func loadView() {
for(var i = 0; i < 10; i++) {
let tempButton = SKSpriteNode(imageNamed: "Button\(i)")
tempButton.position = CGPoint(x: yourValue, y: yourValue)
tempButton.size = CGSize(width: yourValue, height: yourValue)
tempButton.name = "\(i)"
temButton.zPosition = 2
_button.append(tempButton)
self.addChild(_button.last!)
}
}
This will work if you have no other SKSpriteNode in the game because in the touches began I assume that the sprite touched has a number (0-9). To counter this problem, you could add an if statement to verify that the name is between 0-9.
Hope this help!
You should make buttons in the mainstoryboard, and connect them to the view controller. Then you can write the func for each button inside the connected actions
hope this helps
How can one unselect a given segment in a UISegmented control, by pressing the same segment again?
E.g. Press segment 0, it becomes selected and remains highlighted. Press segment 0 again and it becomes unselected and unhighlighted.
The control only fires UIControlEventValueChanged events. Other events don't seem to work with it.
There is a property 'momentary' which when set to YES almost allows the above behavior, excepting that highlighting is only momentary. When momentary=YES pressing the same segment twice results in two UIControlEventValueChanged events, but when momentary=NO only the first press of a given segment results in a UIControlEventValueChanged event being fired. I.e. subsequent presses on the same segment will not fire the UIControlEventValueChanged event.
you can subclass UISegmentedControl:
Swift 3
class ReselectableSegmentedControl: UISegmentedControl {
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let previousSelectedSegmentIndex = self.selectedSegmentIndex
super.touchesEnded(touches, withEvent: event)
if previousSelectedSegmentIndex == self.selectedSegmentIndex {
if let touch = touches.first {
let touchLocation = touch.locationInView(self)
if CGRectContainsPoint(bounds, touchLocation) {
self.sendActionsForControlEvents(.ValueChanged)
}
}
}
}
}
Swift 4
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let previousSelectedSegmentIndex = self.selectedSegmentIndex
super.touchesEnded(touches, with: event)
if previousSelectedSegmentIndex == self.selectedSegmentIndex {
let touch = touches.first!
let touchLocation = touch.location(in: self)
if bounds.contains(touchLocation) {
self.sendActions(for: .valueChanged)
}
}
}
and then
#IBAction func segmentChanged(sender: UISegmentedControl) {
if (sender.selectedSegmentIndex == selectedSegmentIndex) {
sender.selectedSegmentIndex = UISegmentedControlNoSegment;
selectedSegmentIndex = UISegmentedControlNoSegment;
}
else {
selectedSegmentIndex = sender.selectedSegmentIndex;
}
}
Not EXACTLY what you ask for but I was searching for a similar feature and decided on this.
Add doubleTap gesture to UISegmentedControl that sets the selectedSegmentIndex
When you initialize your UISegmentedControl.
let doubleTap = UITapGestureRecognizer(target: self, action #selector(YourViewController.doubleTapToggle))
doubleTap.numberOfTapsRequired = 2
yourSegmentedControl.addGestureRecognizer(doubleTap)
Function to deselect segment
#objc func doubleTapToggle () {
yourSegmentedControl.selectedSegmentIndex = -1
yourSegmentedControl.sendActions(for: valueChanged)
}
Based on #protspace answer, a simpler way, without having to write anything in the #IBAction:
class DeselectableSegmentedControl: UISegmentedControl {
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let previousSelectedSegmentIndex = selectedSegmentIndex
super.touchesEnded(touches, with: event)
if previousSelectedSegmentIndex == selectedSegmentIndex {
let touch = touches.first!
let touchLocation = touch.location(in: self)
if bounds.contains(touchLocation) {
selectedSegmentIndex = UISegmentedControl.noSegment
sendActions(for: .valueChanged)
}
}
}
}
UISegmentedControl can be programmatically deselected with Swift using #IBOutlet weak var mySegmentedControl: UISegmentedControl! and mySegmentedControl.selectedSegmentIndex = -1. As far as detecting the touch of the segment, I do not know. Hopefully someone else will have that answer.