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!
Related
Why can't I use other pencils or colors as expected in this app? It only draws a black color. This is my code:
import UIKit
import PencilKit
import PhotosUI
class ViewController: UIViewController, PKCanvasViewDelegate, PKToolPickerObserver {
#IBOutlet weak var pencilButton: UIBarButtonItem!
#IBOutlet weak var canvasView: PKCanvasView!
let canvasWidth: CGFloat = 768
let canvasOverScrollHeight: CGFloat = 500
let drawing = PKDrawing()
override func viewDidLoad() {
super.viewDidLoad()
canvasView.drawing = drawing
canvasView.delegate = self
canvasView.alwaysBounceVertical = true
canvasView.drawingPolicy = .anyInput
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let toolPicker = PKToolPicker()
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
canvasView.becomeFirstResponder()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let canvasScale = canvasView.bounds.width / canvasWidth
canvasView.minimumZoomScale = canvasScale
canvasView.maximumZoomScale = canvasScale
canvasView.zoomScale = canvasScale
updateContentSizeForDrawing()
canvasView.contentOffset = CGPoint(x: 0, y: -canvasView.adjustedContentInset.top)
}
override var prefersHomeIndicatorAutoHidden: Bool{
return true
}
#IBAction func fingerOrPencil (_ sender: Any) {
canvasView.allowsFingerDrawing.toggle()
pencilButton.title = canvasView.allowsFingerDrawing ? "Finger" : "Pencil"
}
#IBAction func saveToCameraRoll(_ sender: Any) {
UIGraphicsBeginImageContextWithOptions(canvasView.bounds.size, false, UIScreen.main.scale)
canvasView.drawHierarchy(in: canvasView.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if image != nil {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: image!)
}, completionHandler: {success, error in
})
}
}
func updateContentSizeForDrawing() {
let drawing = canvasView.drawing
let contentHeight: CGFloat
if !drawing.bounds.isNull {
contentHeight = max(canvasView.bounds.height, (drawing.bounds.maxY + self.canvasOverScrollHeight) * canvasView.zoomScale)
} else {
contentHeight = canvasView.bounds.height
}
canvasView.contentSize = CGSize(width: canvasWidth * canvasView.zoomScale, height: contentHeight)
}
// Delegate Methods
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
updateContentSizeForDrawing()
}
func canvasViewDidEndUsingTool(_ canvasView: PKCanvasView) {
}
func canvasViewDidFinishRendering(_ canvasView: PKCanvasView) {
}
func canvasViewDidBeginUsingTool(_ canvasView: PKCanvasView) {
}
}
These are the outputs in the console:
2023-01-04 18:34:04.429420+0300 Drawing[45460:449613] [Assert] UINavigationBar decoded as unlocked for UINavigationController, or navigationBar delegate set up incorrectly. Inconsistent configuration may cause problems. navigationController=<UINavigationController: 0x123024000>, navigationBar=<UINavigationBar: 0x12140a0a0; frame = (0 47; 0 50); opaque = NO; autoresize = W; layer = <CALayer: 0x6000030afae0>> delegate=0x123024000
2023-01-04 18:34:04.468831+0300 Drawing[45460:449613] Metal API Validation Enabled
2023-01-04 18:34:04.705019+0300 Drawing[45460:449613] [ToolPicker] Missing defaults dictionary to restore state for: PKPaletteNamedDefaults
2023-01-04 18:35:00.196200+0300 Drawing[45460:449613] Keyboard cannot present view controllers (attempted to present <UIColorPickerViewController: 0x121846e00>)
toolPicket released when out of method scope.
You should have a instance of toolPicker in ViewController.
class ViewController: UIViewController {
let toolPicker = PKToolPicker()
...
}
i solved my problem by changing
toolPicker.addObserver(self)
into
toolPicker.addObserver(canvasView)
and adding the toolPicker at the top as #noppefoxwolf suggested
I am creating a drawing app. This app allows user to draw on the view. i name that view drawing view in the code given below. To create this app I have use BeizerPath and ShapeLayer, whenever user touches the screen the UIBeizerPath and CAShapeLayer is initialized. The Problem with this app is after some drawing view gets Lagged. I cannot figure out what is happening. Is there better way to optimize this app?
CanvasViewController: UIViewController {
// MARK: Properties
#IBOutlet weak var verticalScrollView: UIScrollView!
var lastPoint:CGPoint? // var mutablePath:CGMutablePath?
var drawLine:UIBezierPath?
var shapeLayer:CAShapeLayer?
let dropDown = DropDown()
var shape:CAShapeLayer?
let listOfDropDownMenu:[String] = ["Create New Canvas","Save","Change Color"]
var presenter: CanvasModuleInterface?
// MARK: IBOutlets
#IBOutlet weak var drawingView: UIView!
#IBOutlet weak var showColorViewControllerDropDownButton: UIBarButtonItem!
// MARK: VC's Life cycle
override func viewDidLoad() {
super.viewDidLoad()
self.setup()
self.verticalScrollView.isUserInteractionEnabled = false
}
// MARK: IBActions
// MARK: Other Functions
#IBAction func ShowColorViewControllerAction(_ sender: Any) {
self.dropDown.anchorView = self.showColorViewControllerDropDownButton
self.dropDown.dataSource = self.listOfDropDownMenu
self.dropDown.selectionAction = { [unowned self] (index: Int, item: String) in
switch index{
case 0:
self.createNewView()
case 1:
self.saveTheCanvas()
case 2:
self.presentToColorViewController()
//self.presentColorController()
default:
print("out of order !!!")
}
}
self.dropDown.show()
}
private func createNewView(){
//Mark: - create new canvas
self.drawingView.layer.sublayers!.forEach { (layers) in
layers.removeFromSuperlayer()
}
}
private func presentToColorViewController(){
// Mark : - naviagate to color view controller.
}
private func saveTheCanvas(){
// Mark: - save the drawing
}
private func setup() {
// all setup should be done here
}
private func presentColorController(){
let colorSelectionController = EFColorSelectionViewController()
let colorNavigationController = UINavigationController(rootViewController: colorSelectionController)
colorNavigationController.navigationBar.backgroundColor = UIColor.white
colorNavigationController.navigationBar.isTranslucent = false
colorNavigationController.modalPresentationStyle = UIModalPresentationStyle.popover
colorSelectionController.delegate = self
colorSelectionController.color = self.view.backgroundColor ?? UIColor.white
if UIUserInterfaceSizeClass.compact == self.traitCollection.horizontalSizeClass {
let doneButton: UIBarButtonItem = UIBarButtonItem(
title: NSLocalizedString("Done", comment: ""),
style: UIBarButtonItem.Style.done,
target: self,
action: #selector(dismissViewController)
)
colorSelectionController.navigationItem.rightBarButtonItem = doneButton
}
self.present(colorNavigationController, animated: true, completion: nil)
}
#objc func dismissViewController(){
//##ask confusion.....
self.dismiss(animated: true, completion: nil)
}
}
// MARK: CanvasViewInterface extension CanvasViewController: CanvasViewInterface {
} extension CanvasViewController: EFColorSelectionViewControllerDelegate{
func colorViewController(_ colorViewCntroller: EFColorSelectionViewController, didChangeColor color: UIColor) {
} } extension CanvasViewController{
//Mark: - this extension contains function for drawing the circle.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touches begin")
self.drawLine = UIBezierPath()
self.shapeLayer = CAShapeLayer()
// self.mutablePath = CGMutablePath()
if let touchesPoint = touches.first{
self.lastPoint = touchesPoint.location(in: self.drawingView)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// self.drawingView.layer.sublayers!.forEach { (layer) in // print(layer) // }
// print(self.drawingView.layer)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?){ // print("\(self.drawingView)") // print("\(self.drawingView.layer.sublayers)")
var nextPoint:CGPoint?
if let touchesPoint = touches.first{
nextPoint = touchesPoint.location(in: self.drawingView)
guard let lastLinePoint = self.lastPoint , let nextLinePoint = nextPoint else{return}
self.drawLineInDrawingView(from: lastLinePoint, to: nextLinePoint)
}
if let newPoint = nextPoint{
self.lastPoint = newPoint
}
}
func drawLineInDrawingView(from:CGPoint,to:CGPoint){
drawLine!.move(to: CGPoint(x: from.x, y: from.y))
drawLine!.addLine(to: CGPoint(x: to.x, y: to.y))
shapeLayer!.path = drawLine!.cgPath
shapeLayer!.strokeColor = UIColor.black.cgColor
shapeLayer!.lineCap = .round
shapeLayer!.lineWidth = 100
self.drawingView.layer.addSublayer(shapeLayer!) // print(self.drawingView.layer.sublayers)
}
}
Without knowing what CanvasViewController you are are using, one can't entirely answer your question correctly, but I will answer, assuming it works similar to any regular UIView in iOS.
There are a few things I can point out to improve performance:
First of all, don't add the same ShapeLayer over and over inside drawLineInDrawingView(from:CGPoint,to:CGPoint) to the drawing view, instead do so only once in touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) and store a reference to it.
Secondly your drawLineInDrawingView(from:CGPoint,to:CGPoint) should look like this:
func drawLineInDrawingView(from:CGPoint,to:CGPoint){
CATransaction.begin()
CATransaction.setDisableActions(true)
drawLine!.move(to: CGPoint(x: from.x, y: from.y))
drawLine!.addLine(to: CGPoint(x: to.x, y: to.y))
shapeLayer!.path = drawLine!.cgPath
shapeLayer!.strokeColor = UIColor.black.cgColor
shapeLayer!.lineCap = .round
shapeLayer!.lineWidth = 100
shapeLayer!.setNeedsDisplay()
CATransaction.commit()
}
So in conclusion: Upon touchesBegan() you add the shapeLayer and store a reference to it. Inside touchesMoved, you call the drawLineInDrawingsFunction.
Your drawLineInDrawingsFunction, only updates your Shapelayer with shapeLayer.setNeedsDisplay().
Putting your transformation inside
CATransaction.begin()
CATransaction.setDisableActions(true)
/**Your transformation here**/
CATransaction.commit()
Is only there to stop your View from automatically animating changes, and instead show them immediately.
I hope this helps.
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.
whenever I click a textfield inside the view, then click the other text field, the view disappears. Strange... Can anyone help?
I animate the view using facebook pop. Here is my animation engine code:
import UIKit
import pop
class AnimationEngine {
class var offScreenRightPosition: CGPoint {
return CGPoint(x: UIScreen.main.bounds.width + 250,y: UIScreen.main.bounds.midY - 75)
}
class var offScreenLeftPosition: CGPoint{
return CGPoint(x: -UIScreen.main.bounds.width,y: UIScreen.main.bounds.midY - 75)
}
class var offScreenTopPosition: CGPoint{
return CGPoint(x: UIScreen.main.bounds.midX,y: -UIScreen.main.bounds.midY)
}
class var screenCenterPosition: CGPoint {
return CGPoint(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY - 75)
}
let ANIM_DELAY : Int = 1
var originalConstants = [CGFloat]()
var constraints: [NSLayoutConstraint]!
init(constraints: [NSLayoutConstraint]) {
for con in constraints {
originalConstants.append(con.constant)
con.constant = AnimationEngine.offScreenRightPosition.x
}
self.constraints = constraints
}
func animateOnScreen(_ delay: Int) {
let time = DispatchTime.now() + Double(Int64(Double(delay) * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: time) {
var index = 0
repeat {
let moveAnim = POPSpringAnimation(propertyNamed: kPOPLayoutConstraintConstant)
moveAnim?.toValue = self.originalConstants[index]
moveAnim?.springBounciness = 8
moveAnim?.springSpeed = 8
if (index < 0) {
moveAnim?.dynamicsFriction += 10 + CGFloat(index)
}
let con = self.constraints[index]
con.pop_add(moveAnim, forKey: "moveOnScreen")
index += 1
} while (index < self.constraints.count)
}
}
class func animateToPosisition(_ view: UIView, position: CGPoint, completion: ((POPAnimation?, Bool) -> Void)!) {
let moveAnim = POPSpringAnimation(propertyNamed: kPOPLayerPosition)
moveAnim?.toValue = NSValue(cgPoint: position)
moveAnim?.springBounciness = 8
moveAnim?.springSpeed = 8
moveAnim?.completionBlock = completion
view.pop_add(moveAnim, forKey: "moveToPosition")
}
}
Then here is my viewcontroller code where the view is inside in:
import UIKit
import pop
class LoginVC: UIViewController, UITextFieldDelegate {
override var prefersStatusBarHidden: Bool {
return true
}
#IBOutlet weak var emailLoginVCViewConstraint: NSLayoutConstraint!
#IBOutlet weak var emailLoginVCView: MaterialView!
#IBOutlet weak var emailAddressTextField: TextFieldExtension!
#IBOutlet weak var passwordTextField: TextFieldExtension!
var animEngine : AnimationEngine!
override func viewDidAppear(_ animated: Bool) {
self.emailLoginVCView.isUserInteractionEnabled = true
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.bringSubview(toFront: emailAddressTextField)
self.animEngine = AnimationEngine(constraints: [emailLoginVCViewConstraint])
self.emailAddressTextField.delegate = self
self.passwordTextField.delegate = self
emailAddressTextField.allowsEditingTextAttributes = false
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if (textField === emailAddressTextField) {
passwordTextField.becomeFirstResponder()
} else if (textField === passwordTextField) {
passwordTextField.resignFirstResponder()
} else {
// etc
}
return true
}
#IBAction func emailTapped(_ sender: AnyObject) {
AnimationEngine.animateToPosisition(emailLoginVCView, position: AnimationEngine.screenCenterPosition, completion: { (POPAnimation, Bool)
in
})
}
#IBAction func exitTapped(_ sender: AnyObject) {
AnimationEngine.animateToPosisition(emailLoginVCView, position: AnimationEngine.offScreenRightPosition, completion: { (POPAnimation, Bool)
in
})
}
}
Last here is my hierchy and options: (my view's name is emailLoginVCView). Also when I was debugging when I clicked another textfield I set a breakpoint so I got this info: enter image description here
I have a constraint that binds the center of the login view with the center of the main screen
when I create the AnimationEngine,I pass it that constraint, and it sets its constant to be the offScreenRightPosition.x
when I bring up the email login sheet, I'm not changing the constant of the constraint; I'm just changing the position of the view
which means that autolayout thinks it’s supposed to still be offscreen
when the second textfield becomes active, that’s somehow triggering auto-layout to re-evaluate the constraints, and it sees that the login view’s position doesn’t match what the constraint says it should be so....
Autolayout moves it offscreen
So if I add this in emailTapped(_:), the problem goes away :)
#IBAction func emailTapped(_ sender: AnyObject) {
AnimationEngine.animateToPosisition(emailLoginVCView, position: AnimationEngine.screenCenterPosition, completion: { (POPAnimation, Bool)
in
self.emailLoginVCViewConstraint.constant = 0
})
}
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.