Ok so I have a scrollView which has been subclassed to be able to be applied to any scene which is from a previous question I asked here:
SpriteKit, Swift 2.0 - ScrollView in reverse
import Foundation
import SpriteKit
/// Nodes touched
var nodesTouched: [AnyObject] = [] // global
/// Scroll direction
enum ScrollDirection: Int {
case None = 0
case Vertical
case Horizontal
}
class CustomScrollView: UIScrollView {
// MARK: - Static Properties
/// Touches allowed
static var disabledTouches = false
/// Scroll view
private static var scrollView: UIScrollView!
// MARK: - Properties
/// Current scene
private weak var currentScene: SKScene?
/// Moveable node
private var moveableNode: SKNode?
/// Scroll direction
private var scrollDirection = ScrollDirection.None
// MARK: - Init
init(frame: CGRect, scene: SKScene, moveableNode: SKNode, scrollDirection: ScrollDirection) {
print("Scroll View init")
super.init(frame: frame)
CustomScrollView.scrollView = self
self.scrollDirection = scrollDirection
self.currentScene = scene
self.moveableNode = moveableNode
self.frame = frame
indicatorStyle = .White
scrollEnabled = true
//self.minimumZoomScale = 1
//self.maximumZoomScale = 3
canCancelContentTouches = false
userInteractionEnabled = true
delegate = self
// flip for spritekit (only needed for horizontal)
if self.scrollDirection == .Horizontal {
let flip = CGAffineTransformMakeScale(-1,-1)
self.transform = flip
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Touches
extension CustomScrollView {
/// began
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("Touch began scroll view")
guard !CustomScrollView.disabledTouches else { return }
currentScene?.touchesBegan(touches, withEvent: event)
}
/// moved
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("Touch moved scroll view")
guard !CustomScrollView.disabledTouches else { return }
currentScene?.touchesMoved(touches, withEvent: event)
}
/// ended
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("Touch ended scroll view")
guard !CustomScrollView.disabledTouches else { return }
currentScene?.touchesEnded(touches, withEvent: event)
}
/// cancelled
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
print("Touch cancelled scroll view")
guard !CustomScrollView.disabledTouches else { return }
currentScene?.touchesCancelled(touches, withEvent: event)
}
}
// MARK: - Touch Controls
extension CustomScrollView {
/// Disable
class func disable() {
print("Disabled scroll view")
CustomScrollView.scrollView?.userInteractionEnabled = false
CustomScrollView.disabledTouches = true
}
/// Enable
class func enable() {
print("Enabled scroll view")
CustomScrollView.scrollView?.userInteractionEnabled = true
CustomScrollView.disabledTouches = false
}
}
// MARK: - Delegates
extension CustomScrollView: UIScrollViewDelegate {
/// did scroll
func scrollViewDidScroll(scrollView: UIScrollView) {
print("Scroll view did scroll")
if scrollDirection == .Horizontal {
moveableNode!.position.x = scrollView.contentOffset.x
} else {
moveableNode!.position.y = scrollView.contentOffset.y
}
}
}
The only problem is that the scrollview uses pages, while, the way I want it to look, scrolls only through the sprites, like the raywenderlich one where all the sprites are the only things moving and so I don't have to scroll across multiple pages to get to a sprite.
the project can be found here:
Raywenderlich Project
Because they use their gameViewController I am having trouble figuring out how to implement it through a subclass scrollview like I have above.
I dont understand what you are asking here. I just checked the RayWenderlich tutorial and its exactly the same as my GitHub sample project. They just keep the sprites closer together where as in my project for demonstration purposes I put each sprite on a new page.
If you just want sprites to scroll that just add the sprites to the moveableNode and the rest as usual to the scene directly.
addChild(background)
moveableNode.addChild(sprite1)
Than change the sprite positions so they are closer together. You basically add the 1st sprite to the 1st page in the scrollView and than position the other sprites based on the previous sprites x position. You add these sprites to sprite1 as well.
let sprite1 = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 50, height: 50))
sprite1.position = CGPointMake(0, 0)
page1ScrollView.addChild(sprite1)
let sprite2 = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 50, height: 50))
sprite2.position = CGPointMake(sprite1.position.x + (sprite2.size.width * 1.5), sprite1.position.y)
sprite1.addChild(sprite2)
let sprite3 = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 50, height: 50))
sprite3.position = CGPointMake(sprite2.position.x + (sprite3.size.width * 1.5), sprite1.position.y)
sprite1.addChild(sprite3)
I updated my gitHub project to show this in action
https://github.com/crashoverride777/Swift2-SpriteKit-UIScrollView-Helper
Related
Update[11/1/2018]:
I try to test the code step by step and found that:
The method touchesEnded was never called for the leftover UIView.
Same for the method touchesCancelled
I am following the Apple Documentation on implementing a multitouch app.
The url is :
https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/handling_touches_in_your_view/implementing_a_multitouch_app
I am using the 4 methods:
- touchesBegan(:with:),
- touchesMoved(:with:),
- touchesEnded(:with:),
- touchesCancelled(:with:)
to create a UIView on the touching location when touchesBegan is called. Update the UIView location when touchesMoved is called. And finally, remove the UIView when touchesEnded and touchedCancelled is called.
The problem is when I am tapping really fast on the screen, the UIView doesn't remove from the superview. The code is basically the same as the code provided in the URL with different graphics. I add another view which offset a little bit from the touch location to have a better look on the touch point.
Example of the leftover UIView
Another Image, same code as the comment below
I don't understand what's wrong and why is this happening. I suspect that touchesEnded() was never called but I don't know why. And I would like to ask is there any method to prevent this from happening (no leftover UIView)?
I am using XCode Version 9.2 (9C40b) and iPad Air 2.
I have try the app on different iPad model and same thing happen.
Part of the codes:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
createViewForTouch(touch: touch)
}
}
func createViewForTouch( touch : UITouch ) {
let newView = TouchSpotView() //TouchSportView is a custom UIView
newView.bounds = CGRect(x: 0, y: 0, width: 1, height: 1)
newView.center = touch.location(in: self)
// Add the view and animate it to a new size.
addSubview(newView)
UIView.animate(withDuration: 0.2) {
newView.bounds.size = CGSize(width: 100, height: 100)
}
// Save the views internally
touchViews[touch] = newView //touchViews is a dict, i.e. touchView[UITouch:TouchSportView]
}
func removeViewForTouch (touch : UITouch ) {
if let view = touchViews[touch] {
view.removeFromSuperview()
touchViews.removeValue(forKey: touch)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
removeViewForTouch(touch: touch)
}
}
For the full code, please see the attached URL for apple documentation.
Many thanks for your generous help!!!!
Tried following code (copy pasted from the link you provided) and it works perfectly both on actual device and simulator:
import UIKit
class ViewController: UIViewController {
override func loadView() {
view = TouchableView()
view.backgroundColor = .white
}
}
class TouchableView: UIView {
var touchViews = [UITouch:TouchSpotView]()
override init(frame: CGRect) {
super.init(frame: frame)
isMultipleTouchEnabled = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isMultipleTouchEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
createViewForTouch(touch: touch)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let view = viewForTouch(touch: touch)
// Move the view to the new location.
let newLocation = touch.location(in: self)
view?.center = newLocation
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
removeViewForTouch(touch: touch)
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
removeViewForTouch(touch: touch)
}
}
func createViewForTouch( touch : UITouch ) {
let newView = TouchSpotView()
newView.bounds = CGRect(x: 0, y: 0, width: 1, height: 1)
newView.center = touch.location(in: self)
// Add the view and animate it to a new size.
addSubview(newView)
UIView.animate(withDuration: 0.2) {
newView.bounds.size = CGSize(width: 100, height: 100)
}
// Save the views internally
touchViews[touch] = newView
}
func viewForTouch (touch : UITouch) -> TouchSpotView? {
return touchViews[touch]
}
func removeViewForTouch (touch : UITouch ) {
if let view = touchViews[touch] {
view.removeFromSuperview()
touchViews.removeValue(forKey: touch)
}
}
}
class TouchSpotView : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.lightGray
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Update the corner radius when the bounds change.
override var bounds: CGRect {
get { return super.bounds }
set(newBounds) {
super.bounds = newBounds
layer.cornerRadius = newBounds.size.width / 2.0
}
}
}
So, if you are doing something more with it, add that additional code to the question, or if you removed something from the code, let us know. Otherwise, it should work.
EDIT
For completeness I am including a GitHub link to a minimal project, that works for me.
Instead of finding why touchesCancelled and touchesEnded were not called, using event?.allTouches method to check all touch which UITouch is not being handled.
So I found out how to make a button draggable using the UIPanGestureRecognizer. But the only way I know how to do it is by storing and dragging the button by the center. The problem with this is if you try and drag the button from a corner, the button instantly shifts from the corner to the center. What I'm looking for is a solution that would keep my finger on a selected place while moving without instantly locking onto the center.
The code I'm currently using:
func buttonDrag(pan: UIPanGestureRecognizer) {
print("Being Dragged")
if pan.state == .began {
print("panIF")
buttonCenter = button.center // store old button center
}else {
print("panELSE")
let location = pan.location(in: view) // get pan location
button.center = location // set button to where finger is
}
}
Thanks in advance.
This can be done at least in two different ways, one using GestureRecognizer your question way and other way is subclassing the UIView and implementing the touchesBegan, touchesMoved , touchesEnded, touchesCancelled in general will work for any UIView subclass can be UIButton or UILabel or UIImageView etc...
In your way, using GestureRecognizer I make a few changes you still require a var to keep the origin CGPoint of the touch in your UIButton so we get the touch position relative to the UIButton and when the drag continue adjust the UIButton origin according to the origin touch position and the positions of the movement
Method 1 GestureRecognizer
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
var buttonOrigin : CGPoint = CGPoint(x: 0, y: 0)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let gesture = UIPanGestureRecognizer(target: self, action: #selector(buttonDrag(pan:)))
self.button.addGestureRecognizer(gesture)
}
func buttonDrag(pan: UIPanGestureRecognizer) {
print("Being Dragged")
if pan.state == .began {
print("panIF")
buttonOrigin = pan.location(in: button)
}else {
print("panELSE")
let location = pan.location(in: view) // get pan location
button.frame.origin = CGPoint(x: location.x - buttonOrigin.x, y: location.y - buttonOrigin.y)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Method 2 UIView subclass in this case UIButton subclass
Use this UIButton subclass
import UIKit
class DraggableButton: UIButton {
var localTouchPosition : CGPoint?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let touch = touches.first
self.localTouchPosition = touch?.preciseLocation(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
let touch = touches.first
guard let location = touch?.location(in: self.superview), let localTouchPosition = self.localTouchPosition else{
return
}
self.frame.origin = CGPoint(x: location.x - localTouchPosition.x, y: location.y - localTouchPosition.y)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
self.localTouchPosition = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
self.localTouchPosition = nil
}
}
Result
Hope this helps you
What I'm trying to achieve:
Trying to grab the coordinate of the currently touched area on the screen and draw the received coordinate on the screen. Simply put, a basic drawing app that's all written programmatically (for my own practice).
Problem
touchesBegan and touchesMoved for PaintingSubclass are not getting called at all.
Current setup
I have a PaintingViewController then I also have a PaintingSubclass.
PaintingViewController is a UIViewController and PaintingSubclass is a UIView.
PaintingViewController creates an instance of PaintingSubclass and adds it to the subview of PaintingViewController.
PaintingSubclass is where the actual drawing happens.
What I've tried so far
Put a breakpoint inside the touchesMoved and touchesBegan (didn't work)
Tried to add a UITapGestureRecognizer (didn't work)
Enabled self.isUserInteractionEnabled = true (didn't work)
Make PaintingSubclass inherit UIControl and call sendActions(for: .valueChanged) at the end of touchesMoved and touchesBegan (didn't work)
Current Code
(please ignore any the unnecessary variables)
import UIKit
class PaintingViewController: UIViewController{
var _paintView: PaintingSubclass? = nil
override func loadView() {
view = UILabel()
}
private var labelView: UILabel {
return view as! UILabel
}
override func viewDidLoad() {
_paintView = PaintingSubclass()
_paintView?.frame = CGRect(x: view.bounds.minX, y: view.bounds.minY, width: 400, height: 750)
_paintView?.backgroundColor = UIColor.white
_paintView?.setNeedsDisplay()
view.addSubview(_paintView!)
}
class PaintingSubclass: UIView{
private var context: CGContext? = nil
var styleSelection: String = "buttcap"
var linewidth: Float = 5
var lineCapStyle: CGLineCap? = nil
var lineJoinStyle: CGLineJoin? = nil
var lineWidthValue: CGFloat? = nil
var colorValue: UIColor? = nil
override init(frame: CGRect) {
super.init(frame: frame)
lineWidthValue = 0.5
colorValue = UIColor(cgColor: UIColor.black.cgColor)
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
context = UIGraphicsGetCurrentContext()!
context?.move(to: CGPoint(x: 0.0, y: 110.0))
context?.setStrokeColor((colorValue?.cgColor)!)
context?.drawPath(using: CGPathDrawingMode.stroke)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let touch: UITouch = touches.first!
let touchPoint: CGPoint = touch.location(in: self)
let _xValue = touchPoint.x
let _yValue = touchPoint.y
NSLog("coordinate: \(_xValue), \(_yValue)")
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
let touch: UITouch = touches.first!
let touchPoint: CGPoint = touch.location(in: self)
let _xValue = touchPoint.x
let _yValue = touchPoint.y
NSLog("coordinate: \(_xValue), \(_yValue)")
}
}
}
What am I doing wrong here? Been stuck in this state for hours not making any progress. Any help/feedbacks will be much appreciated.
I don't know why you are setting your default view to UILabel(), but your default isUserInteractionEnabled of your view is false. Set it to true
override func loadView() {
view = UILabel()
view.isUserInteractionEnabled = true
}
According to your code, your PaintingViewController's view is a UILabel, the property isUserInteractionEnabled default is false.Try to set this property to true may help you.
The problem lies on:
// override func loadView() {
// view = UILabel()
// }
//
// private var labelView: UILabel {
// return view as! UILabel
// }
You turn the view controller's self view object to a UILabel object.
It would be better to have your painting view as the main view in the view controller and the UILabel as a subview of the painting view. The UILabel needs to have userInteractionEnabled in your current layout, but instead of changing that switch the layout around.
Apart from anything else, presumably you don't want the painting view to draw over the label, but rather the label to be drawn on top, so the label should be the subview.
I'm in swift 3 and I have a class that's a subclass of UIScrollView. Here it is:
import SpriteKit
/// Scroll direction
enum ScrollDirection {
case vertical
case horizontal
}
class CustomScrollView: UIScrollView {
// MARK: - Static Properties
/// Touches allowed
static var disabledTouches = false
/// Scroll view
private static var scrollView: UIScrollView!
// MARK: - Properties
/// Current scene
private let currentScene: SKScene
/// Moveable node
private let moveableNode: SKNode
/// Scroll direction
private let scrollDirection: ScrollDirection
/// Touched nodes
private var nodesTouched = [AnyObject]()
// MARK: - Init
init(frame: CGRect, scene: SKScene, moveableNode: SKNode, scrollDirection: ScrollDirection) {
self.currentScene = scene
self.moveableNode = moveableNode
self.scrollDirection = scrollDirection
super.init(frame: frame)
CustomScrollView.scrollView = self
self.frame = frame
delegate = self
indicatorStyle = .white
isScrollEnabled = true
isUserInteractionEnabled = true
//canCancelContentTouches = false
//self.minimumZoomScale = 1
//self.maximumZoomScale = 3
if scrollDirection == .horizontal {
let flip = CGAffineTransform(scaleX: -1,y: -1)
transform = flip
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// Began
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("begin " + String(CustomScrollView.disabledTouches))
for touch in touches {
let location = touch.location(in: currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches began in current scene
currentScene.touchesBegan(touches, with: event)
/// Call touches began in all touched nodes in the current scene
nodesTouched = currentScene.nodes(at: location)
for node in nodesTouched {
node.touchesBegan(touches, with: event)
}
}
}
/// Moved
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("moved " + String(CustomScrollView.disabledTouches))
for touch in touches {
let location = touch.location(in: currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches moved in current scene
currentScene.touchesMoved(touches, with: event)
/// Call touches moved in all touched nodes in the current scene
nodesTouched = currentScene.nodes(at: location)
for node in nodesTouched {
node.touchesMoved(touches, with: event)
}
}
}
/// Ended
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches ended in current scene
currentScene.touchesEnded(touches, with: event)
/// Call touches ended in all touched nodes in the current scene
nodesTouched = currentScene.nodes(at: location)
for node in nodesTouched {
node.touchesEnded(touches, with: event)
}
}
}
/// Cancelled
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("cancelled " + String(CustomScrollView.disabledTouches))
for touch in touches {
let location = touch.location(in: currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches cancelled in current scene
currentScene.touchesCancelled(touches, with: event)
/// Call touches cancelled in all touched nodes in the current scene
nodesTouched = currentScene.nodes(at: location)
for node in nodesTouched {
node.touchesCancelled(touches, with: event)
}
}
}
}
// MARK: - Touch Controls
extension CustomScrollView {
/// Disable
class func disable() {
CustomScrollView.scrollView?.isUserInteractionEnabled = false
CustomScrollView.disabledTouches = true
}
/// Enable
class func enable() {
CustomScrollView.scrollView?.isUserInteractionEnabled = true
CustomScrollView.disabledTouches = false
}
}
// MARK: - Delegates
extension CustomScrollView: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollDirection == .horizontal {
moveableNode.position.x = scrollView.contentOffset.x
} else {
moveableNode.position.y = scrollView.contentOffset.y
}
}
}
It's main function is to create a scrollable menu, and for the most part it works. I create an object of it in GameScene, and how it's supposed to work is that when a touch is registered, the overridden touch functions (touchBegan, touchMoved, etc.) in CustomScrollView are called, which then call the touch functions in GameScene. This does happen, the menu scrolls fine, and GameScene's methods ARE called.
The catch is my overridden functions (and GameScene's) are only called when you're swiping horizontally. When you swipe up or down (past a certain degree), the menu still scrolls but I think that it's UIScrollView's touch methods that are being called.
When you swipe vertically, my touchCancelled method gets called, which makes me think this has something to do with UIScrollView's gesture recognizers (the pan/drag recognizer I think) firing when they shouldn't be.
Is this the case? If so, can I disable the recognizer? And if I can, should I? On a side note, is this the best (or at least an acceptable) way to implement UIScrollView so that GameScene's touch methods are still called?
If the conflicting gesture recognizers need to be recognized simultaneously, you can make use of gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:),
Hi I currently have a horizontal UIScrollView that allows me to pick a character in my game and then select it but the problem that I have now is I am trying to get the scrollview to stop at the point where the sprite/character is in the middle of the screen and instead of having it stop anywhere.
My UIScrollView has a “moveable node” that contains multiple pages that hold all the sprites as seen below:
scrollViewHorizontal = CustomScrollView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height), scene: self, moveableNode: moveableNodeHorizontal, scrollDirection: .Horizontal)
scrollViewHorizontal.contentSize = CGSizeMake(self.frame.size.width * 4, self.frame.size.height) // * 4 makes it three times as wide as screen
view?.addSubview(scrollViewHorizontal)
scrollViewHorizontal.hidden = true
addChild(moveableNodeHorizontal)
moveableNodeHorizontal.hidden = true
moveableNodeHorizontal.zPosition = 100000
scrollViewHorizontal.setContentOffset(CGPoint(x: 0 + self.frame.size.width + self.frame.size.width * 2, y: 0), animated: false)
let page1ScrollView = SKSpriteNode(color: SKColor.clearColor(), size: CGSizeMake(scrollViewHorizontal.frame.size.width, scrollViewHorizontal.frame.size.height))
page1ScrollView.zPosition = -1
page1ScrollView.position = CGPointMake(CGRectGetMidX(self.frame) - (self.frame.size.width * 3.5), CGRectGetMidY(self.frame) - (self.frame.height / 2))
moveableNodeHorizontal.addChild(page1ScrollView)
let page2ScrollView = SKSpriteNode(color: SKColor.clearColor(), size: CGSizeMake(scrollViewHorizontal.frame.size.width, scrollViewHorizontal.frame.size.height))
page2ScrollView.zPosition = -1
page2ScrollView.position = CGPointMake(CGRectGetMidX(self.frame) - (self.frame.size.width * 2.5), CGRectGetMidY(self.frame) - (self.frame.height / 2))
moveableNodeHorizontal.addChild(page2ScrollView)
Characters.append(generateCharacters(CGPointMake(0, 0), page:(page1ScrollView), tex: "YellowFrog"))
Characters.append(generateCharacters(CGPointMake(100, 0), page:(page1ScrollView), tex: "Frog"))
Characters.append(generateCharacters(CGPointMake(0, 0), page:(page2ScrollView), tex: "ball"))
Characters.append(generateCharacters(CGPointMake(100, 0), page:(page2ScrollView), tex: "ball"))
I searched this previously before asking the question and it was recommended that I use this:
func scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
But I have no idea how to implement it into my code because my scrollview is contained in another class which can be seen here:
/// Nodes touched
var nodesTouched: [AnyObject] = [] // global
/// Scroll direction
enum ScrollDirection: Int {
case None = 0
case Vertical
case Horizontal
}
/// Custom UIScrollView class
class CustomScrollView: UIScrollView {
// MARK: - Static Properties
/// Touches allowed
static var disabledTouches = false
/// Scroll view
private static var scrollView: UIScrollView!
// MARK: - Properties
/// Nodes touched. This will forward touches to node subclasses.
private var nodesTouched = [AnyObject]()
/// Current scene
private let currentScene: SKScene
/// Moveable node
private let moveableNode: SKNode
/// Scroll direction
private let scrollDirection: ScrollDirection
// MARK: - Init
init(frame: CGRect, scene: SKScene, moveableNode: SKNode, scrollDirection: ScrollDirection) {
self.currentScene = scene
self.moveableNode = moveableNode
self.scrollDirection = scrollDirection
super.init(frame: frame)
CustomScrollView.scrollView = self
self.frame = frame
delegate = self
indicatorStyle = .White
scrollEnabled = true
userInteractionEnabled = true
//canCancelContentTouches = false
//self.minimumZoomScale = 1
//self.maximumZoomScale = 3
if scrollDirection == .Horizontal {
let flip = CGAffineTransformMakeScale(-1,-1)
transform = flip
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Touches
extension CustomScrollView {
/// Began
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//super.touchesBegan(touches, withEvent: event)
for touch in touches {
let location = touch.locationInNode(currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches began in current scene
currentScene.touchesBegan(touches, withEvent: event)
/// Call touches began in all touched nodes in the current scene
nodesTouched = currentScene.nodesAtPoint(location)
for node in nodesTouched {
node.touchesBegan(touches, withEvent: event)
}
}
}
/// Moved
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
//super.touchesMoved(touches, withEvent: event)
for touch in touches {
let location = touch.locationInNode(currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches moved in current scene
currentScene.touchesMoved(touches, withEvent: event)
/// Call touches moved in all touched nodes in the current scene
nodesTouched = currentScene.nodesAtPoint(location)
for node in nodesTouched {
node.touchesMoved(touches, withEvent: event)
}
}
}
/// Ended
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
//super.touchesEnded(touches, withEvent: event)
for touch in touches {
let location = touch.locationInNode(currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches ended in current scene
currentScene.touchesEnded(touches, withEvent: event)
/// Call touches ended in all touched nodes in the current scene
nodesTouched = currentScene.nodesAtPoint(location)
for node in nodesTouched {
node.touchesEnded(touches, withEvent: event)
}
}
}
/// Cancelled
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
//super.touchesCancelled(touches, withEvent: event)
for touch in touches! {
let location = touch.locationInNode(currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches cancelled in current scene
currentScene.touchesCancelled(touches, withEvent: event)
/// Call touches cancelled in all touched nodes in the current scene
nodesTouched = currentScene.nodesAtPoint(location)
for node in nodesTouched {
node.touchesCancelled(touches, withEvent: event)
}
}
}
}
// MARK: - Touch Controls
extension CustomScrollView {
/// Disable
class func disable() {
CustomScrollView.scrollView?.userInteractionEnabled = false
CustomScrollView.disabledTouches = true
}
/// Enable
class func enable() {
CustomScrollView.scrollView?.userInteractionEnabled = true
CustomScrollView.disabledTouches = false
}
}
// MARK: - Delegates
extension CustomScrollView: UIScrollViewDelegate {
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollDirection == .Horizontal {
moveableNode.position.x = scrollView.contentOffset.x
} else {
moveableNode.position.y = scrollView.contentOffset.y
}
}
}
EDIT:
extension CustomScrollView: UIScrollViewDelegate {
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollDirection == .Horizontal {
moveableNode.position.x = scrollView.contentOffset.x
} else {
moveableNode.position.y = scrollView.contentOffset.y
}
}
}
CustomScrollView is a project showed how UIKit could be used in sprite-kit but this causes a lot of work during rendering that slows your game (due to low fps) so it's advisable and reasonable to use it just only for your game menus, not for the game.
A way to iOS8.x:
You can build for example many SKSpriteNode backgrounds that follow the one with the other and in your update method doing:
override func update(currentTime: CFTimeInterval) {
self.enumerateChildNodesWithName("background") { node, _ in
if node is SKSpriteNode {
if let p = (node as! SKShapeNode).position {
// adjust your background positions to make the scroll
}
}
}
}
A way to iOS9x:
Apple added the SKCameraNode class to sprite-kit in iOS 9 to make it easier to scroll and zoom in sprite-kit scenes.
var gameCamera = SKCameraNode()
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let previousLocation = touch.previousLocationInNode(self)
let deltaX = location.x - previousLocation.x
gameCamera.position.x += deltaX
}
}
The CustomScrollView page selector.
Having said that we come to your situation, suppose you have all your pages in array called pages and you want to stop the scroll to the x page:
func scrollToPage(page:Int) {
let totalPages : Int = (pages.count - 1)
let reversedPage : Int = totalPages - page
self.scrollView.setContentOffset(CGPointMake(round(self.frame.size.width*CGFloat(reversedPage)), scrollView.contentOffset.y),animated: true)
}
Another good idea it's to intercept the scroll delegate methods to update your elements:
func scrollViewDidScroll(scrollView: UIScrollView) {
self.scrollView.scrollViewDidScroll(scrollView)
updateLayout() // call my method to update elements in scroll checking the current page
}
You could obtain your page number whit this code:
extension UIScrollView {
var currentPage: Int {
return abs(Int(round((contentSize.width - self.contentOffset.x) / self.bounds.size.width))-1)
}
}