Scrollview stopping at a certain point

2019-09-15 05:48发布

问题:

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
    }
  }
}

回答1:

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)
    }
}