I have this UIViewController in which i've overrided the touchBegan and touchEnded functions. I also have a button that segueues (push) to another view controller with an SKView on it. But the overrided function in the first controller are still active ei. the calculations done there are still showing on the second ViewController.
Maybe theres something im missing or something im assuming thats wrong. Any help would be appreciated
This is the first view controller
import UIKit
import CoreMotion
class PortraitViewController: UIViewController {
var vc: UIViewController?
var startPoint: CGPoint?
var endPoint: CGPoint?
var movedPoint: CGPoint?
var previousMove = CGPoint(x: 0, y: 0)
var beginTouch: UITouch?
var scaleSum = 0
var isPortrait = true
let DEBUG: Bool = true
// MARK:
// MARK: Overriden Variables
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask{return .portrait}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{return .portrait}
override var shouldAutorotate: Bool {return false}
#IBAction func pinch(_ sender: UIPinchGestureRecognizer) {
if (isPortrait){
let scale = sender.scale
scaleSum += scale.exponent
print(scaleSum)
if(scaleSum > 10) {
scaleSum = 0
print(">10")
}
else if(scaleSum < -10) {
print("<10")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isPortrait){
if let theTouch = touches.first {
endPoint = theTouch.location(in: self.view)
let diffx = (endPoint!.x - startPoint!.x)
let diffy = (endPoint!.y - startPoint!.y)
if(diffx != 0 && diffy != 0){
let vector = CGVector(dx: diffx, dy: diffy)
var angle = atan2(vector.dy, vector.dx) * CGFloat(180.0 / M_PI)
if angle < 0 { angle *= -1 } else { angle = 360 - angle }
}
}
}
super.touchesEnded(touches, with: event)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isPortrait){
if let theTouch = touches.first {
startPoint = theTouch.location(in: self.view)
}
}
super.touchesBegan(touches, with: event)
}
//One of my attempts to jerry rig a solution
#IBAction func prepToLandscape(_ sender: UIBarButtonItem) {
self.isPortrait = false
print("isPortrait = \(self.isPortrait)")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
This is the second view controller
import UIKit
import CoreMotion
import SpriteKit
class LandScapeViewController: UIViewController {
var vc: UIViewController?
// MARK:
// MARK: Overriden Variables
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask{return .landscape}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{return .landscapeLeft
// MARK:
// MARK: Functions
override func viewDidLoad() {
super.viewDidLoad()
let controllerStoryBoard = UIStoryboard(name: "Main", bundle: nil)
vc = controllerStoryBoard.instantiateViewController(withIdentifier: "Root")
// Part of my attempt to jerry rig a solution
let vcP: PortraitViewController = UIStoryboard(name:"Controller",bundle: nil).instantiateViewController(withIdentifier: "PortraitController") as! PortraitViewController
vcP.isPortrait = false
print("vcP.isPortrait = \(vcP.isPortrait)")
}
override func viewWillAppear(_ animated: Bool) {
self.view.isMultipleTouchEnabled = true
let scene = GameScene(size: joystickView.bounds.size)
scene.backgroundColor = .gray
if let skView = joystickView as? SKView {
skView.showsFPS = false
skView.ignoresSiblingOrder = true
skView.backgroundColor = .red
skView.presentScene(scene)
}
navigationController?.setNavigationBarHidden(true, animated: false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func toPortrait(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: {() -> Void in
}
}
Assuming the code shown is everything relevant, this makes sense. What's happening is this: the touch events are hit-tested to the joystickView, but because you haven't implemented a custom touchesBegan(_:with:) on that view, the touch event is passed up to the next UIResponder in the responder chain. That would be the LandScapeViewController. But that class also doesn't implement a custom touchesBegan(_:with:), so the event passes to the next class, which in this case is PortraitViewController. Because PortraitViewController does implement that method, it gets called. There's your confusion.
To fix this, implement the touches… methods on UIResponder for either joystickView or LandScapeViewController, even if they do nothing – but don't call super in them! Note the following, from the touchesBegan(_:with:) documentation:
If you override this method without calling super (a common use pattern), you must also override the other methods for handling touch events, even if your implementations do nothing.
Where you're overriding touchesBegan(_:with:), you probably don't want to call super. This is because the super implementation is the one that says "oh, shoot, I don't know how to handle this – pass it up the chain!" But when you handle the touch, it should end there, because you're handling it! So only call super when you're not handling the touch – which in your case looks like never, at least for PortraitViewController.
For more information, check out Event Delivery: The Responder Chain.
Related
Hi there I am completing some research for Alzheimers - I want to be able to record the time it takes to complete a drawing. Both the time spend with apple pencil on tablet and the time spent overall to complete a drawing (time on tablet plus time in between strokes).
I have created this application so far but can't get the timer to work.
I have the drawing/scribble board down pat.
I have tried many different approaches but I am just not experienced enough in code to work out why it is not starting the timer when the apple pencil hits the tablet. The code below is for the ViewController script.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var canvasView: CanvasView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func clearCanvas(_ sender: Any) { //this button will clear canvas and clear timers!
canvasView.clearCanvas()
timer.invalidate()
seconds = 0 //Here we manually enter the restarting point for the seconds, but it would be wiser to make this a variable or constant.
timeDrawing.text = "\(seconds)"
}
#IBOutlet weak var timeDrawing: UILabel! //This is one of the label used to display the time drawing (i have yet to add the label to display the time inbetween strokes)
var seconds = 0
var timer = Timer()
var isTimerRunning = false //This will be used to make sure only one timer is created at a time.
var resumeTapped = false
var touchPoint:CGPoint!
var touches:UITouch!
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
seconds += 1
timeDrawing.text = "\(seconds)" //This will update the label.
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let touches = touch.location(in: canvasView) // or maybe ...(in: self)
if touch.type == .pencil {
if !isTimerRunning {
runTimer()
} else {
timer.invalidate()
}
}
}
}
I was hoping when the apple pencil touched the tablet it would start the timer. And then when the pencil left the tablet it would stop one of the timers. (i have yet to add another timer for the time inbetween strokes, any help with that would be appreciated too.)
As #ElTomato said you're looking for touchesEnded.
You have to move timer.invalidate() from touchesBegan to touchesEnded.
Also you should change your updateTimer logic for more high accuracy.
private(set) var from: Date?
#objc func updateTimer() {
let to = Date()
let timeIntervalFrom = (from ?? to).timeIntervalSince1970
let time = to.timeIntervalSince1970 - timeIntervalFrom
timeDrawing.text = "\(round(time))" //This will update the label.
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard let touch = touches.first else { return }
let touches = touch.location(in: canvasView) // or maybe ...(in: self)
if touch.type == .pencil {
if !isTimerRunning {
from = Date()
runTimer()
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
if !isTimerRunning {
timer.invalidate()
timer = nil
from = nil
timeDrawing.text = "0"
}
}
Here is how it works for the code below. 1. Drag a UIView control from the object library onto the storyboard. While this control is selected, enter 'TouchView' in the class field under the identity inspector. Make an IBOutlet connection to the view controller, naming it touchView.
import UIKit
class ViewController: UIViewController, TouchViewDelegate {
// MARK: - Variables
// MARK: - IBOutlet
#IBOutlet weak var touchView: TouchView!
// MARK: - IBAction
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
/* delegate */
touchView.touchViewDelegate = self
}
// MARK: - Protocol
var startDate: Date?
func touchBegan(date: Date) {
startDate = date
}
func touchEnd(date: Date) {
if let myDate = startDate {
let components = Calendar.current.dateComponents([.second], from: myDate, to: date)
if let secs = components.second {
print(secs)
}
}
}
}
import UIKit
protocol TouchViewDelegate: class {
func touchBegan(date: Date)
func touchEnd(date: Date)
}
class TouchView: UIView {
weak var touchViewDelegate: TouchViewDelegate?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = event?.allTouches?.first
if let touchPoint = touch?.location(in: self) {
if self.bounds.contains(touchPoint) {
Swift.print("You have started a session")
touchViewDelegate?.touchBegan(date: Date())
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
Swift.print("You have ended the session.")
touchViewDelegate?.touchEnd(date: Date())
}
}
So when the user touches the touch view, TouchView will make a delegate call to the view controller. When the user gets his finger off off the touch view, it will make another call to the view controller. And the view controller will calculate the difference between two dates. So you don't need a timer. It's my person opinion, but you should stay away from Timer unless you have no alternatives.
ScrollView is not recognising shake gesture
I have 3 files.
1.ContentVC - consists of scrollView to swipe between viewcontrollers
2.FirstVC - It contains shakegesture function
3.SecondVC - default view controller
When i shake the device nothing happens.
ContentVC.swift
class ContainerVC: UIViewController {
#IBOutlet weak var scroll: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let left = self.storyboard?.instantiateViewController(withIdentifier: "vw") as! UINavigationController
self.addChild(left)
self.scroll.addSubview(left.view)
self.didMove(toParent: self)
let last = self.storyboard?.instantiateViewController(withIdentifier: "lastviewNav") as! UINavigationController
self.addChild(last)
self.scroll.addSubview(last.view)
self.didMove(toParent: self)
var middleframe:CGRect = last.view.frame
middleframe.origin.x = self.view.frame.width
last.view.frame = middleframe
self.scroll.contentSize = CGSize(width: (self.view.frame.width) * 2, height: (self.view.frame.height))
}
}
FirstVC.swift
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if event?.subtype == UIEvent.EventSubtype.motionShake {
if motion == .motionShake {
print("quote is appearing by shaking the device")
} else {
print("No quote is coming")
}
}
}
Motion events are delivered initially to the first responder and are forwarded up the responder chain as appropriate.
https://developer.apple.com/documentation/uikit/uiresponder/1621090-motionended
So make sure that:
FirstVC able to became first responder:
override var canBecomeFirstResponder: Bool {
return true
}
FirstVC is first responder at specific point of time:
firstVC.becomeFirstResponder()
Now your UIViewController will be able to receive shake motion events.
You can read more about Responder chain: How do Responder Chain Works in IPhone? What are the "next responders"?
I try to detect a tap on an UIImageView while it is in the process of animation, but it does't work.
What I do (swift 4):
added UIImageView via StoryBoard:
#IBOutlet weak var myImageView: UIImageView!
doing animation:
override func viewWillAppear (_ animated: Bool) {
super.viewWillAppear (animated)
myImageView.center.y + = view.bounds.height
}
override func viewDidAppear (_ animated: Bool) {
super.viewDidAppear (animated)
UIView.animate (withDuration: 10, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.myImageView.center.y - = self.view.bounds.height
})
}
try to detect the tap:
override func viewDidLoad () {
super.viewDidLoad ()
let gestureSwift2AndHigher = UITapGestureRecognizer (target: self, action: #selector (self.actionUITapGestureRecognizer))
myImageView.isUserInteractionEnabled = true
myImageView.addGestureRecognizer (gestureSwift2AndHigher)
}
#objc func actionUITapGestureRecognizer () {
print ("actionUITapGestureRecognizer - works!")
}
Please, before voting for a question, make sure that there are no normally formulated answers to such questions, understandable to the beginner and written in swift above version 2, so I can not apply them for my case.
Studying this problem, I realized that it is necessary to also tweak the frame !? But this is still difficult for me. Tell me, please, what I need to add or change in the code below.
Thank you for your help.
class ExampleViewController: UIViewController {
#IBOutlet weak var myImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// action by tap
let gestureSwift2AndHigher = UITapGestureRecognizer(target: self, action: #selector (self.actionUITapGestureRecognizer))
myImageView.isUserInteractionEnabled = true
myImageView.addGestureRecognizer(gestureSwift2AndHigher)
}
// action by tap
#objc func actionUITapGestureRecognizer (){
print("actionUITapGestureRecognizer - works!") // !!! IT IS DOES NOT WORK !!!
}
// hide UIImageView before appear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
myImageView.center.y += view.bounds.height
}
// show UIImageView after appear with animation
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 10, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.myImageView.center.y -= self.view.bounds.height
})
}
}
To detect touch on a moving (animated) view, simply override hitTest using the presentation layer:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return (layer.presentation()!.frame)
.contains(self.convert(point, to: superview!)) ? self : nil
}
In the example at hand
It works with any and all gesture recognizers
DO NOT modify any frames, or anything else, at the view controller level
Simply subclass the view itself, adding the override above
Don't forget that naturally, if you want to stop the animation once the item is grabbed, do that (in your view controller) with yourPropertyAnimator?.stopAnimation(true) , yourPropertyAnimator = nil
You CANNOT do what you want using UITapGestureRecognizer because it uses frame based detection and detects if a touch was inside your view by checking against its frame..
The problem with that, is that animations already set the view's final frame before the animation even begins.. then it animates a snapshot of your view into position before showing your real view again..
Therefore, if you were to tap the final position of your animation, you'd see your tap gesture get hit even though your view doesn't seem like it's there yet.. You can see that in the following image:
https://i.imgur.com/Wl9WRfV.png
(Left-Side is view-hierarchy inspector)..(Right-Side is the simulator animating).
To solve the tapping issue, you can try some sketchy code (but works):
import UIKit
protocol AnimationTouchDelegate {
func onViewTapped(view: UIView)
}
protocol AniTouchable {
var animationTouchDelegate: AnimationTouchDelegate? {
get
set
}
}
extension UIView : AniTouchable {
private struct Internal {
static var key: String = "AniTouchable"
}
private class func getAllSubviews<T: UIView>(view: UIView) -> [T] {
return view.subviews.flatMap { subView -> [T] in
var result = getAllSubviews(view: subView) as [T]
if let view = subView as? T {
result.append(view)
}
return result
}
}
private func getAllSubviews<T: UIView>() -> [T] {
return UIView.getAllSubviews(view: self) as [T]
}
var animationTouchDelegate: AnimationTouchDelegate? {
get {
return objc_getAssociatedObject(self, &Internal.key) as? AnimationTouchDelegate
}
set {
objc_setAssociatedObject(self, &Internal.key, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let touchLocation = touch.location(in: self)
var didTouch: Bool = false
let views = self.getAllSubviews() as [UIView]
for view in views {
if view.layer.presentation()?.hitTest(touchLocation) != nil {
if let delegate = view.animationTouchDelegate {
didTouch = true
delegate.onViewTapped(view: view)
}
}
}
if !didTouch {
super.touchesBegan(touches, with: event)
}
}
}
class ViewController : UIViewController, AnimationTouchDelegate {
#IBOutlet weak var myImageView: UIImageView!
deinit {
self.myImageView.animationTouchDelegate = nil
}
override func viewDidLoad() {
super.viewDidLoad()
self.myImageView.isUserInteractionEnabled = true
self.myImageView.animationTouchDelegate = self
}
func onViewTapped(view: UIView) {
print("Works!")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
myImageView.center.y += view.bounds.height
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 5, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.myImageView.center.y -= self.view.bounds.height
})
}
}
It works by overriding touchesBegan on the UIView and then checking to see if any of the touches landed inside that view.
A MUCH better approach would be to just do it in the UIViewController instead..
import UIKit
protocol AnimationTouchDelegate : class {
func onViewTapped(view: UIView)
}
extension UIView {
private class func getAllSubviews<T: UIView>(view: UIView) -> [T] {
return view.subviews.flatMap { subView -> [T] in
var result = getAllSubviews(view: subView) as [T]
if let view = subView as? T {
result.append(view)
}
return result
}
}
func getAllSubviews<T: UIView>() -> [T] {
return UIView.getAllSubviews(view: self) as [T]
}
}
class ViewController : UIViewController, AnimationTouchDelegate {
#IBOutlet weak var myImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.myImageView.isUserInteractionEnabled = true
}
func onViewTapped(view: UIView) {
print("Works!")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
myImageView.center.y += view.bounds.height
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 5, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.myImageView.center.y -= self.view.bounds.height
})
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let touchLocation = touch.location(in: self.view)
var didTouch: Bool = false
for view in self.view.getAllSubviews() {
if view.isUserInteractionEnabled && !view.isHidden && view.alpha > 0.0 && view.layer.presentation()?.hitTest(touchLocation) != nil {
didTouch = true
self.onViewTapped(view: view)
}
}
if !didTouch {
super.touchesBegan(touches, with: event)
}
}
}
I'm working on a simple app that allows the user draw using one of four provided colors and share/export their work. To change the brush size, you must enter a "settings" view and adjust it via slider. The problem I'm having is that the value of the slider does not affect the size of the brush upon returning to the main view. Whatever I set the initial brush size to be, thats how it remains no matter how many times I mess with the slider.
The two views are managed by a NavigationViewController if that makes a difference.
Main/Drawing View Controller
import UIKit
class DrawingViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var buttonsStackView: UIStackView!
var lastPoint = CGPoint.zero
var red : CGFloat = 0.0
var green : CGFloat = 0.0
var blue : CGFloat = 0.0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(DrawingViewController.appBecameActive), name: UIApplicationDidBecomeActiveNotification, object: nil)
blueTapped(UIButton())
}
func appBecameActive() {
self.buttonsStackView.hidden = false
}
override func viewWillAppear(animated: Bool) {
self.navigationController?.navigationBarHidden = true
print("Current Brush Size: \(SettingsViewController().brushSize)")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let point = touch.locationInView(self.imageView)
self.lastPoint = point
}
self.buttonsStackView.hidden = true
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let point = touch.locationInView(self.imageView)
//print("Point: \(point)")
drawBetweenPoints(self.lastPoint, secondPoint: point)
self.lastPoint = point
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let point = touch.locationInView(self.imageView)
//print(point)
drawBetweenPoints(self.lastPoint, secondPoint: point)
}
self.buttonsStackView.hidden = false
}
func drawBetweenPoints(firstPoint:CGPoint, secondPoint:CGPoint) {
UIGraphicsBeginImageContext(self.imageView.frame.size)
let context = UIGraphicsGetCurrentContext()
self.imageView.image?.drawInRect(CGRect(x: 0, y: 0, width: self.imageView.frame.size.width, height: self.imageView.frame.size.height))
CGContextMoveToPoint(context, firstPoint.x, firstPoint.y)
CGContextAddLineToPoint(context, secondPoint.x, secondPoint.y)
//randomTapped(UIButton())
CGContextSetRGBStrokeColor(context, self.red, self.green, self.blue, 1.0)
CGContextSetLineCap(context, .Round)
CGContextSetLineWidth(context, CGFloat(SettingsViewController().brushSize))
CGContextStrokePath(context)
self.imageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
func eraseEasel() {
self.imageView.image = nil
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "drawingToSettingsSegue" {
let settingsVC = segue.destinationViewController as! SettingsViewController
settingsVC.drawingVC = self
}
}
#IBAction func blueTapped(sender: AnyObject) {
self.red = 56 / 255
self.green = 109 / 255
self.blue = 229 / 255
}
#IBAction func greenTapped(sender: AnyObject) {
self.red = 37 / 255
self.green = 235 / 255
self.blue = 114 / 255
}
#IBAction func redTapped(sender: AnyObject) {
self.red = 229 / 255
self.green = 56 / 255
self.blue = 56 / 255
}
#IBAction func yellowTapped(sender: AnyObject) {
self.red = 249 / 255
self.green = 215 / 255
self.blue = 23 / 255
}
#IBAction func randomTapped(sender: AnyObject) {
self.red = CGFloat(arc4random_uniform(255)) / 255
self.green = CGFloat(arc4random_uniform(255)) / 255
self.blue = CGFloat(arc4random_uniform(255)) / 255
}
}
Settings View Controller
import UIKit
class SettingsViewController: UIViewController {
weak var drawingVC : DrawingViewController? = nil
#IBOutlet weak var brushSlider: UISlider!
var brushSize : Float = 15
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationController?.navigationBarHidden = false
}
override func viewWillAppear(animated: Bool) {
brushSlider.value = self.brushSize
}
#IBAction func eraseTapped(sender: AnyObject) {
self.drawingVC?.eraseEasel()
self.navigationController?.popViewControllerAnimated(true)
}
#IBAction func shareTapped(sender: AnyObject) {
if let image = drawingVC?.imageView.image {
let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil)
self.presentViewController(activityVC, animated: true, completion: nil)
}
}
#IBAction func brushSlider(sender: AnyObject) {
self.brushSize = brushSlider.value
print(self.brushSize)
}
}
You're always getting a new brush size, which is the default value.
SettingsViewController().brushSize
This line creates a new SettingsViewController object and gets the brushSize value from it which is 15.
Create an outlet at the top of your main view controller and use that one if you want to use an instance of the brush size that doesn't persist between app runs.
var settingsVC = SettingsViewController()
then replace all of the
SettingsViewController().brushSize
with
settingsVC.brushSize
However, even after you do this, you are instantiating your SettingsViewController object from a storyboard. So that will be a completely difference instance than the one you create at the top of your main view controller until you segue to the settings view, unless it is an embed segue. If you want the initial brush size to always be 15, and to only update after you have been to the settings screen, then you can set your local reference in your prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "drawingToSettingsSegue" {
let settingsVC = segue.destinationViewController as! SettingsViewController
settingsVC.drawingVC = self
self.settingsVC = settingsVC
}
}
Good luck, hopefully this helps!
Explanation
I'm trying to build a character selection menu similar to Crossy Road's one (as you can see here). So I found this iCarousel, which would help me with all of it, but everything I read talk about implementing it to a ViewController, which isn't my case. I'm using GameScene and I didn't found anything talking about it. Is there anyway I could implement it to my game? or even another effect similar to the character selection menu I mentioned above?
Attempt (beyowulf)
You can download it here.
GameScene.swift
import SpriteKit
class GameScene: SKScene {
var show = SKSpriteNode()
var hide = SKSpriteNode()
func showCharPicker(){
NSNotificationCenter.defaultCenter().postNotificationName("showCharPicker", object: nil)
}
func hideCharPicker(){
NSNotificationCenter.defaultCenter().postNotificationName("hideCharPicker", object: nil)
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
print("didMoveToView")
show = SKSpriteNode(imageNamed: "show")
show.anchorPoint = CGPointZero
show.position = CGPointZero
addChild(show)
hide = SKSpriteNode(imageNamed: "hide")
hide.anchorPoint = CGPointZero
hide.position = CGPoint(x: self.frame.width / 2 - hide.frame.width / 2, y: 0)
addChild(hide)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches{
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node == show{
print("show")
showCharPicker()
}
else if node == hide{
print("hide")
hideCharPicker()
}
}
}
}
GameViewController.swift
import UIKit
import SpriteKit
class GameViewController: UIViewController, iCarouselDataSource, iCarouselDelegate{
var squaresArray : NSMutableArray = NSMutableArray()
#IBOutlet weak var carousel: iCarousel!
deinit{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func showCarousel(){
self.carousel.hidden = false
}
func hideCarousel(){
self.carousel.hidden = true
}
override func viewDidLoad(){
super.viewDidLoad()
// Configure iCarousel
carousel.dataSource = self
carousel.delegate = self
carousel.type = .CoverFlow
carousel.reloadData()
self.carousel.hidden = true
// Register showCarousel and hideCarousel functions
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.showCarousel), name: "showCharPicker", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.hideCarousel), name: "hideCharPicker", object: nil)
// Configure view
let skView = SKView()
self.view.insertSubview(skView, belowSubview: self.carousel)
skView.frame = self.view.bounds
// Additionals
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
// Configure scene
let scene = GameScene(size:self.view.bounds.size)
scene.scaleMode = .ResizeFill
scene.size = self.view.bounds.size
skView.presentScene(scene)
}
//iCarousel
override func awakeFromNib(){
super.awakeFromNib()
squaresArray = NSMutableArray(array: ["square1","square2","square3"])
}
func numberOfItemsInCarousel(carousel: iCarousel) -> Int{
return squaresArray.count
}
func carousel(carousel:iCarousel, didSelectItemAtIndex index:NSInteger){
//self.hideCarousel()
}
func carousel(carousel: iCarousel, viewForItemAtIndex index: Int, reusingView view: UIView?) -> UIView{
var itemView: UIImageView
if (view == nil){
itemView = UIImageView(frame:CGRect(x:0, y:0, width:200, height:200))
itemView.contentMode = .Center
}
else{
itemView = view as! UIImageView;
}
itemView.image = UIImage(named: "\(squaresArray.objectAtIndex(index))")
return itemView
}
func carousel(carousel: iCarousel, valueForOption option: iCarouselOption, withDefault value: CGFloat) -> CGFloat{
if (option == .Spacing){
return value * 2
}
return value
}
}
What's happening:
Thanks in advance,
Luiz.
You can use NSNotifications to show your character picker. You just need to observe the notifications posted by your SKScene. Your viewDidLoad should look something like:
override func viewDidLoad(){
super.viewDidLoad()
carousel.type = .CoverFlow
carousel.reloadData()
let spriteKitView = SKView()
spriteKitView.frame = self.view.bounds
self.view.insertSubview(spriteKitView, belowSubview: self.carousel)
spriteKitView.showsFPS = true
spriteKitView.showsNodeCount = true
spriteKitView.ignoresSiblingOrder = true
self.gameScene = GameScene(size:self.view.bounds.size)
self.gameScene.scaleMode = .AspectFill
self.gameScene.imageName = self.images[0] as! String
self.carousel.hidden = true
spriteKitView.presentScene(self.gameScene)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.showCarousel), name: gameScene.kShowNotification, object: nil)
}
You'll want to implementing carousel(carousel:iCarousel, didSelectItemAtIndex index:NSInteger) so you know what is selected, and so you can return to game play. For example:
func carousel(carousel:iCarousel, didSelectItemAtIndex index:NSInteger)
{
self.gameScene.imageName = self.images[index] as! String
self.hideCarousel()
}
You also need to remove observing before your view controller is deallocated.
deinit
{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Your SKScene can then post a notifications:
import SpriteKit
class GameScene: SKScene {
var imageName = "square1"{
didSet{
self.hidden = false
self.childNode.texture = SKTexture(imageNamed: imageName)
}
}
let kShowNotification = "showPicker"
var childNode = SKSpriteNode()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.childNode = SKSpriteNode(imageNamed: imageName)
self.childNode.anchorPoint = CGPointZero
self.childNode.position = CGPointZero
self.addChild(self.childNode)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.showCharPicker()
}
func showCharPicker()
{
self.hidden = true
NSNotificationCenter.defaultCenter().postNotificationName(kShowNotification, object: nil)
}
}
If you want to change hit detection, you need to subclass the view for which you need it to change. This case your iCarousel view.
You can then either override hitTest or pointInside. I've created an iCarousel subclass and overrode pointInside to only return true when the point is inside one of the carousel's contentView's subviews.
class CarouselSubclass: iCarousel {
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
var inside = false
for view in self.contentView.subviews
{
inside = CGRectContainsPoint(view.frame, point)
if inside
{
return inside
}
}
return inside
}
}
You need to remember to change the class of your carousel in interface builder and update your outlet as well.