Scrollview stopping at a certain point - ios

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

Related

Changing UIImageView transform scale broke movement system

I am trying to create an Image view which I can move and scale on screen. the problem is that when I change the scale of the Image, the movement system seams to be broken.
I wrote some code to drag the object from an anchor point which could be different from the center of the UIImage, but the scale ruined the process.
/*
See LICENSE folder for this sample’s licensing information.
Abstract:
Main view controller for the AR experience.
*/
import ARKit
import SceneKit
import UIKit
import ModelIO
class ViewController: UIViewController, ARSessionDelegate , UIGestureRecognizerDelegate{
// MARK: Outlets
#IBOutlet var sceneView: ARSCNView!
#IBOutlet weak var blurView: UIVisualEffectView!
#IBOutlet weak var dropdown: UIPickerView!
#IBOutlet weak var AddStickerButton: UIButton!
#IBOutlet weak var deleteStickerButton: UIImageView!
var offset : CGPoint = CGPoint.zero
var isDeleteVisible : Bool = false
let array:[String] = ["HappyHeart_Lisa", "Logo_bucato", "Sweety_2_Lisa", "Sweety_Lisa", "Tonglue_Lisa"]
lazy var statusViewController: StatusViewController = {
return childViewControllers.lazy.flatMap({ $0 as? StatusViewController }).first!
}()
var stickers = [Sticker]()
// MARK: Properties
var myScene : SCNScene!
/// Convenience accessor for the session owned by ARSCNView.
var session: ARSession {
sceneView.session.configuration
//sceneView.scene.background.contents = UIColor.black
return sceneView.session
}
var nodeForContentType = [VirtualContentType: VirtualFaceNode]() //Tiene sotto controllo la selezione(Tipo maschera)
let contentUpdater = VirtualContentUpdater() //Chiama la VirtualContentUpdater.swift
var selectedVirtualContent: VirtualContentType = .faceGeometry {
didSet {
// Set the selected content based on the content type.
contentUpdater.virtualFaceNode = nodeForContentType[selectedVirtualContent]
}
}
// MARK: - View Controller Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = contentUpdater
sceneView.session.delegate = self
sceneView.automaticallyUpdatesLighting = true
createFaceGeometry()
// Set the initial face content, if any.
contentUpdater.virtualFaceNode = nodeForContentType[selectedVirtualContent]
// Hook up status view controller callback(s).
statusViewController.restartExperienceHandler = { [unowned self] in
self.restartExperience()
}
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(scale))
let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotate))
pinchGesture.delegate = self
rotationGesture.delegate = self
view.addGestureRecognizer(pinchGesture)
view.addGestureRecognizer(rotationGesture)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
/*
AR experiences typically involve moving the device without
touch input for some time, so prevent auto screen dimming.
*/
UIApplication.shared.isIdleTimerDisabled = true
resetTracking()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
session.pause()
}
// MARK: - Setup
/// - Tag: CreateARSCNFaceGeometry
func createFaceGeometry() {
// This relies on the earlier check of `ARFaceTrackingConfiguration.isSupported`.
let device = sceneView.device!
let maskGeometry = ARSCNFaceGeometry(device: device)!
let glassesGeometry = ARSCNFaceGeometry(device: device)!
nodeForContentType = [
.faceGeometry: Mask(geometry: maskGeometry),
.overlayModel: GlassesOverlay(geometry: glassesGeometry),
.blendShapeModel: RobotHead(),
.sfere: RobotHead()
]
}
// MARK: - ARSessionDelegate
func session(_ session: ARSession, didFailWithError error: Error) {
guard error is ARError else { return }
let errorWithInfo = error as NSError
let messages = [
errorWithInfo.localizedDescription,
errorWithInfo.localizedFailureReason,
errorWithInfo.localizedRecoverySuggestion
]
let errorMessage = messages.flatMap({ $0 }).joined(separator: "\n")
DispatchQueue.main.async {
self.displayErrorMessage(title: "The AR session failed.", message: errorMessage)
}
}
func sessionWasInterrupted(_ session: ARSession) {
blurView.isHidden = false
statusViewController.showMessage("""
SESSION INTERRUPTED
The session will be reset after the interruption has ended.
""", autoHide: false)
}
func sessionInterruptionEnded(_ session: ARSession) {
blurView.isHidden = true
DispatchQueue.main.async {
self.resetTracking()
}
}
/// - Tag: ARFaceTrackingSetup
func resetTracking() {
statusViewController.showMessage("STARTING A NEW SESSION")
guard ARFaceTrackingConfiguration.isSupported else { return }
let configuration = ARFaceTrackingConfiguration()
configuration.isLightEstimationEnabled = true
session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
}
// MARK: - Interface Actions
/// - Tag: restartExperience
func restartExperience() {
// Disable Restart button for a while in order to give the session enough time to restart.
statusViewController.isRestartExperienceButtonEnabled = false
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.statusViewController.isRestartExperienceButtonEnabled = true
}
resetTracking()
}
// MARK: - Error handling
func displayErrorMessage(title: String, message: String) {
// Blur the background.
blurView.isHidden = false
// Present an alert informing about the error that has occurred.
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let restartAction = UIAlertAction(title: "Restart Session", style: .default) { _ in
alertController.dismiss(animated: true, completion: nil)
self.blurView.isHidden = true
self.resetTracking()
}
alertController.addAction(restartAction)
present(alertController, animated: true, completion: nil)
}
//Create a new Sticker
func createNewSticker(){
stickers.append(Sticker(view : self.view, viewCtrl : self))
}
#IBAction func addNewSticker(_ sender: Any) {
createNewSticker()
}
//Function To Move the Stickers, all the Touch Events Listener
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches as! Set<UITouch>) {
var location = touch.location(in: self.view)
for sticker in stickers {
if(sticker.imageView.frame.contains(location) && !isSomeOneMoving()){
//sticker.imageView.center = location
offset = touch.location(in: sticker.imageView)
let offsetPercentage = CGPoint(x: offset.x / sticker.imageView.bounds.width, y: offset.y / sticker.imageView.bounds.height)
let offsetScaled = CGPoint(x: sticker.imageView.frame.width * offsetPercentage.x, y: sticker.imageView.frame.height * offsetPercentage.y)
offset.x = (sticker.imageView.frame.width / 2) - offsetScaled.x
offset.y = (sticker.imageView.frame.height / 2) - offsetScaled.y
location = touch.location(in: self.view)
location.x = (location.x + offset.x)
location.y = (location.y + offset.y)
sticker.imageView.center = location
disableAllStickersMovements()
isDeleteVisible = true
sticker.isStickerMoving = true;
deleteStickerButton.isHidden = false
}
}
}
}
func disableAllStickersMovements(){
for sticker in stickers {
sticker.isStickerMoving = false;
}
}
func isSomeOneMoving() -> Bool{
for sticker in stickers {
if(sticker.isStickerMoving){
return true
}
}
return false
}
var lastLocationTouched : CGPoint = CGPoint.zero
var lastStickerTouched : Sticker = Sticker()
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches as! Set<UITouch>) {
var location = touch.location(in: self.view)
for sticker in stickers {
if(sticker.imageView.frame.contains(location) && sticker.isStickerMoving){
lastLocationTouched = location
location = touch.location(in: self.view)
location.x = (location.x + offset.x)
location.y = (location.y + offset.y)
sticker.imageView.center = location
//sticker.imageView.center = location
}
if(deleteStickerButton.frame.contains(lastLocationTouched) && isDeleteVisible && sticker.isStickerMoving){
sticker.imageView.alpha = CGFloat(0.5)
}else{
sticker.imageView.alpha = CGFloat(1)
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for sticker in stickers {
if(deleteStickerButton.frame.contains(lastLocationTouched) && isDeleteVisible && sticker.isStickerMoving){
removeASticker(sticker : sticker)
disableAllStickersMovements()
}
}
disableAllStickersMovements()
isDeleteVisible = false
deleteStickerButton.isHidden = true
}
func removeASticker(sticker : Sticker ){
sticker.imageView.removeFromSuperview()
let stickerPosition = stickers.index(of: sticker)!
stickers.remove(at: stickerPosition)
for sticker in stickers {
sticker.isStickerMoving = false;
}
}
var identity = CGAffineTransform.identity
#objc func scale(_ gesture: UIPinchGestureRecognizer) {
for sticker in stickers {
if(sticker.isStickerMoving){
switch gesture.state {
case .began:
identity = sticker.imageView.transform
case .changed,.ended:
sticker.imageView.transform = identity.scaledBy(x: gesture.scale, y: gesture.scale)
case .cancelled:
break
default:
break
}
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
#objc func rotate(_ gesture: UIRotationGestureRecognizer) {
for sticker in stickers {
if(sticker.isStickerMoving){
sticker.imageView.transform = sticker.imageView.transform.rotated(by: gesture.rotation)
}
}
}
}
and then the sticker class
import UIKit
import Foundation
class Sticker : NSObject, UIGestureRecognizerDelegate{
var location = CGPoint(x: 0 , y: 0);
var sticker_isMoving = false;
let imageView = UIImageView()
var isStickerMoving : Bool = false;
init(view : UIView, viewCtrl : ViewController ) {
super.init()
imageView.image = UIImage(named: "BroccolFace_Lisa.png")
imageView.isUserInteractionEnabled = true
imageView.contentMode = UIViewContentMode.scaleAspectFit
imageView.frame = CGRect(x: view.center.x, y: view.center.y, width: 200, height: 200)
view.addSubview(imageView)
}
override init(){
}
}
This is because the imageView.bounds and the touch.location(in: imageView) are in unscaled values. This will overcome the problem:
offset = touch.location(in: imageView)
let offsetPercentage = CGPoint(x: offset.x / imageView.bounds.width, y: offset.y / imageView.bounds.height)
let offsetScaled = CGPoint(x: imageView.frame.width * offsetPercentage.x, y: imageView.frame.height * offsetPercentage.y)
offset.x = (imageView.frame.width / 2) - offsetScaled.x
offset.y = (imageView.frame.height / 2) - offsetScaled.y
Basically it converts the offset into a percentage based on the unscaled values and then converts that into scaled values based on the imageView frame (which is modified by the scale). It then uses that to calculate the offset.
EDIT (NUMBER TWO)
This is more complete way to do it and it should solve any issues that may arise due to scaling or rotation.
Add this structure to hold the details of the dragging for images:
struct DragInfo {
let imageView: UIImageView
let startPoint: CGPoint
}
Add these instance variables (you can also remove offset if you want):
var dragStartPoint: CGPoint = CGPoint.zero
var currentDragItems: [DragInfo] = []
var dragTouch: UITouch?
Change touchesBegan to this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard self.dragTouch == nil, let touch = touches.first else { return }
self.dragTouch = touch
let location = touch.location(in: self.view)
self.dragStartPoint = location
for imageView in self.imageList {
if imageView.frame.contains(location) {
self.currentDragItems.append(DragInfo(imageView: imageView, startPoint: imageView.center))
}
}
}
Change touchesMoved to this:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let dragTouch = self.dragTouch else { return }
for touch in touches {
if touch == dragTouch {
let location = touch.location(in: self.view)
let offset = CGPoint(x: location.x - self.dragStartPoint.x, y: location.y - self.dragStartPoint.y)
for dragInfo in self.currentDragItems {
let imageOffSet = CGPoint(x: dragInfo.startPoint.x + offset.x, y: dragInfo.startPoint.y + offset.y)
dragInfo.imageView.center = imageOffSet
}
}
}
}
Change touchesEnded to this:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let dragTouch = self.dragTouch, touches.contains(dragTouch) else { return }
self.currentDragItems.removeAll()
self.dragTouch = nil
}
Set the following properties on the gesture recognisers used:
scaleGesture.delaysTouchesEnded = false
scaleGesture.cancelsTouchesInView = false
rotationGesture.delaysTouchesEnded = false
rotationGesture.cancelsTouchesInView = false
Some explanation about how it works.
With all the touch events it only considers the first touch because dragging from multiple touches doesn't make much sense (what if two touches were over the same image view and move differently). It records this touch and then only considers that touch for dragging things around.
When touchesBegan is called it checks no touch for dragging exists (indicating a drag in progress) and it finds all image views that are under the touch and for each one it records the details of itself and it start centre position in a DragInfo structure and stores it in the currentDragItems array. It also records the position the touch started in the main view and the touch that initiated it.
When touchesMoved is called it only considers the touch that started the dragging and it calculates the offset from the original position the touch started in the main view and then goes down the list of images involved in the dragging and calculates their new centre based on their original starting position and the offset calculated and sets that as the new centre.
When touchesEnded is called assuming it is the dragging touch that is ended it clears the array of DragInfo objects to ready for the next drag.
You need to set the delaysTouchesEnded and cancelsTouchesInView properties on all gesture recognisers so that all touches are passed through to the view otherwise the touchesEnded methods in particular are not called.
Doing the calculations like this removes the problems of scale and rotation as you are just concerned with offsets from initial positions. It also works if multiple image views are dragged at the same time as their details are kept separately.
Now there are some things to be aware of:
You will need to put in all the other code you app required as this is just a basic example to show the idea.
This assumes that you only want to drag image views that you pick up at the start. If you want to collect image views as you drag around you would need to develop a much more complicated system.
As I stated only one drag operation can be in progress at a time and it takes the first touch registered as this source touch. This source touch is then used to filter out any other touches that may happen. This is done to keep things simple and otherwise you would have to account for all kinds of strange situations like if multiple touches were on the same image view.
I hope this all makes sense and you can adapt it to solve your problem.
Here is an extension that I use to pan, pinch and rotate an image with UIPanGestureRecognizer, UIPinchGestureRecognizer and UIRotationGestureRecognizer
extension ViewController : UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func panGesture(gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .ended: fallthrough
case .changed:
let translation = gesture.translation(in: gesture.view)
if let view = gesture.view {
var finalPoint = CGPoint(x:view.center.x + translation.x, y:view.center.y + translation.y)
finalPoint.x = min(max(finalPoint.x, 0), self.myImageView.bounds.size.width)
finalPoint.y = min(max(finalPoint.y, 0), self.myImageView.bounds.size.height)
view.center = finalPoint
gesture.setTranslation(CGPoint.zero, in: gesture.view)
}
default : break
}
}
func pinchGesture(gesture: UIPinchGestureRecognizer) {
switch gesture.state {
case .changed:
let scale = gesture.scale
gesture.view?.transform = gesture.view!.transform.scaledBy(x: scale, y: scale)
gesture.scale = 1
default : break
}
}
func rotateGesture(gesture: UIRotationGestureRecognizer) {
switch gesture.state {
case .changed:
let rotation = gesture.rotation
gesture.view?.transform = gesture.view!.transform.rotated(by: rotation)
gesture.rotation = 0
default : break
}
}
}
setting the UIGestureRecognizerDelegate will help you do the three of gestures at the same time.

Drag UIButton without it shifting to center [Swift 3]

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

Swift 3: UIScrollView and (disabling) Gesture Recognizers

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:),

Unable to move sprite left or right in SpriteKit

https://github.com/bmh3110/JackTheGiantGame
I'm unable to move my sprite left or right when the screen is touched. I'm just starting this game and I can't even get it to work. I've had this problem for a few days and it's getting really frustrating.
GameplayScene.swift
import SpriteKit
class GameplayScene: SKScene {
var player : Player?
var canMove = false
var moveLeft = false
var center : CGFloat?
override func didMoveToView(view: SKView) {
center = (self.scene?.size.width)! / (self.scene?.size.height)!
player = self.childNodeWithName("Player") as? Player!
}
override func update(currentTime: NSTimeInterval) {
managePlayer()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if location.x > center {
moveLeft = false
} else {
moveLeft = true
}
}
canMove = true
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
func managePlayer() {
if canMove {
player?.movePlayer(moveLeft)
}
}
}
Player.swift
import SpriteKit
class Player: SKSpriteNode {
func movePlayer(moveLeft: Bool) {
if moveLeft {
self.position.x = self.position.x - 7
} else {
self.position.x = self.position.x + 7
}
}
}
In the touchesEnded function set canMove = false

Swift 2.0, SpriteKit - Scrollview to not use pages.

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

Resources