Can't touch UIImageView that is moving in Swift - ios

I made drop tile game that tiles fall from the top of screen to one's bottom.
This game system is when you touch a tile, the tile will be hidden.
The tiles are custom class (GameTile class), but Touches Began in GameViewController didn't work.
How can I solve it?
GameTile.swift
class GameTile: UIImageView {
init(named: String, frame: CGRect) {
super.init(frame: frame)
super.image = (UIImage(named: named))
super.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameTileNormal: GameTile {
let namedDefault: String
var frameDefault: CGRect
let isHiddenDefault: Bool
var isUserInteractionEnabledDefault: Bool
let colorName: UIColor
init(
named: String,
frame: CGRect,
isHidden: Bool = false,
isUserInteractionEnabled: Bool = true,
color: UIColor = UIColor.blue) {
namedDefault = named
isHiddenDefault = isHidden
frameDefault = frame
isUserInteractionEnabledDefault = isUserInteractionEnabled
colorName = color
super.init(named: named, frame: frame)
super.isHidden = isHiddenDefault
super.isUserInteractionEnabled = isUserInteractionEnabledDefault
super.backgroundColor = colorName
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
GameView.swift
class GameView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = true
self.backgroundColor = (UIColor.white)
self.frame = CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
//make tiles
let tileNormal = GameTileNormal.init(named: "clear",
frame: CGRect(x:0), y:-60, width:60, height:60),isUserInteractionEnabled: true)
self.addSubview(tileNormal)
//move tiles
moveTile(tile: tileNormal, lane: 1)
}
}
func moveTile(tile: GameTile, lane: Int) {
UIImageView.animate(withDuration: TimeInterval(2.0),
delay: 0.0,
options: .curveLinear,
animations: {
tile.frame.origin.y = UIScreen.main.bounds.size.height
}, completion: {finished in
tile.removeFromSuperview()
//make new tile
self.makeTiles(lane: lane)
})
}
GameViewController.swift
class GameViewController: UIViewController {
var gameView: GameView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.isUserInteractionEnabled = true
gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.view.addSubview(trapView)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
if let gameView = self.gameView {
// touchEvent.view is "gameView", not the view whose kind of class is GameTileNormal...
if let touchedGameTile = touchEvent.view as? GameTileNormal {
print("Touched normal tile")
touchEvent.view?.isHidden = true
touchEvent.view?.isUserInteractionEnabled = false
}else{
// other view
}
}
}
UPDATE
I changed how to move tiles from UIImageView.animation to Timer.
Then If I touched tiles, it didn't through after if (tile.layer.presentation()?.hitTest(location)) != nil { in touchesBegan, GameViewController.....
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
let location = touchEvent.location(in: touchEvent.view)
if let standardView = self.standardView {
for tile in standardView.tiles {
//breakpoint stops here
if (tile.layer.presentation()?.hitTest(location)) != nil {
//breakpoint doesn't through here
if tile is GameTileNormal {
//normal tile touched
}else{
}
break
}
}
}
}
moveTiles
makeTileTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(updateTilesPositionY(timer:)), userInfo: sendArray, repeats: true)
update tile position (drop tiles)
#objc func updateTilesPositionY(timer: Timer){
//tile info
let timerInfo:[Any] = timer.userInfo as! [Any]
let tile:GameTile = timerInfo[0] as! GameTile
let lane: Int = timerInfo[1] as! Int
//drop tile
tile.frame.origin.y = tile.frame.origin.y+1
//if tile reached on the bottom
if tile.frame.origin.y >= UIScreen.main.bounds.size.height {
if tile is GameTileNormal {
self.showGameOverView()
}
}else{
//drop tile
}

In UIImageView.animate add option .allowUserInteraction:
UIImageView.animate(withDuration: TimeInterval(2.0),
delay: 0.0,
options: [.curveLinear, .allowUserInteraction],
animations: {
tile.frame.origin.y = UIScreen.main.bounds.size.height
}, completion: {finished in
...
By default the user interaction is disallowed during animations.
UPDATE
However, to test whether the user hit a moving object, you will have a bit harder time. See for example this SO question. Basically, the UIView object does not really move, you can easily test that after firing the animation, the frame of the animated object is set straight to the end position. Just the presentation layer draws the moving view.
You will have to always go over all your moving tiles in the game and test each one if any of them has been touched (here I assume you have a reference to all the tiles in the game):
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
let location = touchEvent.location(in: touchEvent.view)
if let gameView = self.gameView {
for tile in tiles {
// go over all the moving objects in your scene and hit test all of them
if let touchedLayer = tile.layer.presentation()?.hitTest(location) {
// if a hittest returns a layer, it means that this tile was touched, we can handle it and break out of the loop
tile.isHidden = true
tile.isUserInteractionEnabled = false
tile.removeFromSuperview()
break
}
}
}
}

Use layer.presentationLayer to run a hitTest if that hitTest return a CALayer then you are touching that titleView, in fact this will only work if your titles are userInteractionEnabled = false
Full Code
import UIKit
class GameTile: UIImageView {
init(named: String, frame: CGRect) {
super.init(frame: frame)
super.image = (UIImage(named: named))
super.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameTileNormal: GameTile {
let namedDefault: String
var frameDefault: CGRect
let isHiddenDefault: Bool
var isUserInteractionEnabledDefault: Bool
let colorName: UIColor
init(
named: String,
frame: CGRect,
isHidden: Bool = false,
isUserInteractionEnabled: Bool = false,
color: UIColor = UIColor.blue) {
namedDefault = named
isHiddenDefault = isHidden
frameDefault = frame
isUserInteractionEnabledDefault = isUserInteractionEnabled
colorName = color
super.init(named: named, frame: frame)
super.isHidden = isHiddenDefault
super.isUserInteractionEnabled = isUserInteractionEnabledDefault
super.backgroundColor = colorName
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = true
self.backgroundColor = (UIColor.white)
self.frame = CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
//make tiles
let tileNormal = GameTileNormal.init(named: "clear",
frame: CGRect(x:0, y:-60, width:60, height:60),isUserInteractionEnabled: false)
self.addSubview(tileNormal)
//move tiles
moveTile(tile: tileNormal, lane: 1)
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.blue.cgColor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func moveTile(tile: GameTile, lane: Int) {
UIImageView.animate(withDuration: TimeInterval(10),
delay: 0.0,
options: .curveLinear,
animations: {
tile.frame.origin.y = UIScreen.main.bounds.size.height
}, completion: {finished in
tile.removeFromSuperview()
//make new tile
//self.makeTiles(lane: lane)
})
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
let location = touchEvent.location(in: touchEvent.view)
for tile in self.subviews {
// go over all the moving objects in your scene and hit test all of them
if tile.layer.presentation()?.hitTest(location) != nil {
// if a hittest returns a layer, it means that this tile was touched, we can handle it and break out of the loop
tile.isHidden = true
break
}
}
}
}
class ViewController: UIViewController {
var gameView: GameView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.isUserInteractionEnabled = true
gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.view.addSubview(gameView)
}
}

Related

Draw on large UIView take a lot of memory

when I start to draw on large UIView ( width: 3700 , height: 40000 ), it takes a lot of memory
when app starts, memory is 150 MB and when start drawing on it( calling setNeedsDisplay method) take around 1 GB and app is gonna crash
class DrawingVc: UIViewController {
let contentView = DrawableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.backgroundColor = .clear
self.view.addSubview(contentView)
contentView.frame = CGRect(x: 0, y: 0, width:view.frame.width, height:
view.frame.height * 50)
}
here is the code of custom view, as you can see, setNeedsDisplay runs on touchMoves
class DrawableView: UIView {
var mLastPath: UIBezierPath?
weak var scdelegate: DrawableViewDelegate?
var isDrawEnable = true
private var drawingLines : [UIBezierPath] = []
override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
debugPrint("request draw")
drawLine()
}
private func drawLine() {
UIColor.blue.setStroke()
for line in drawingList {
line.lineWidth = 4
line.stroke()
line.lineCapStyle = .round
}
}
var drawingList = [UIBezierPath]()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if touches.count == 2 {
return
}
let location = (touches.first?.location(in: self))!
mLastPath = UIBezierPath()
mLastPath?.move(to: location)
prevPoint = location
drawingList.append(mLastPath!)
}
var prevPoint: CGPoint?
var isFirst = true
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
debugPrint("touchesMoved: " , (touches.first?.location(in: self).x)! , (touches.first?.location(in: self).y)! )
if let coalescedtouches = event?.coalescedTouches(for: touches.first!)
{
for coalescedTouch in coalescedtouches
{
let locationInView = coalescedTouch.location(in: self)
if let prevPoint = prevPoint {
let midPoint = CGPoint( x: (locationInView.x + prevPoint.x) / 2, y: (locationInView.y + prevPoint.y) / 2)
if isFirst {
mLastPath?.addLine(to: midPoint)
}else {
mLastPath?.addQuadCurve(to: midPoint, controlPoint: prevPoint)
}
isFirst = false
} else {
mLastPath?.move(to: locationInView)
}
prevPoint = locationInView
}
}
setNeedsDisplay()
}
}
What makes this problem and how that fix?
Your view is larger than the largest possible screen on an iOS device, so I suppose your view is embedded in a scrollview. You should only draw the visible parts of your view. Unfortunately, this is not supported by UIView directly. You may take a look on CATiledLayer, which supports drawing of only visible parts of a layer, and it supports different levels of details for zoomed layers, too.

SpriteKit device orientation change causes SKLabelNode distortion Swift

I am using SpriteKit to write a game for iOS in Swift. I am still fairly new to SpriteKit.
I would like to support both orientations for both iPhone and iPad, and have found this:
multiple orientations in SpriteKit
This works as expected in the simulator and device, however on device I notice some SKSpriteNodes distort to their new size slightly before the device rotation animation.
This is very noticeable especially with SKLabelNodes where the text distorts either slightly squashed or stretched depending on the orientation change.
I have an idea why the distortion is happening, but confirmation and a fix would be fantastic.
This occurs on device with the code described in the link, but I have updated for swift 3
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size:self.view.bounds.size)
scene.scaleMode = .resizeFill
(self.view as! SKView).presentScene(scene)
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override var prefersStatusBarHidden: Bool {
return true
}
}
class GameScene: SKScene {
var currentNode: CustomNode!
override func didMove(to view: SKView) {
self.backgroundColor = SKColor.white
transitionToScene(sceneType: .Menu)
}
override func didChangeSize(_ oldSize: CGSize) {
currentNode?.layout()
}
func transitionToScene(sceneType: SceneTransition) {
switch sceneType {
case .Menu:
currentNode?.dismissWithAnimation(animation: .Right)
currentNode = MenuNode(gameScene: self)
currentNode.presentWithAnimation(animation: .Right)
case .Scores:
currentNode?.dismissWithAnimation(animation: .Left)
currentNode = ScoresNode(gameScene: self)
currentNode.presentWithAnimation(animation: .Left)
default: fatalError("Unknown scene transition.")
}
}
}
class CustomNode: SKNode {
weak var gameScene: GameScene!
init(gameScene: GameScene) {
self.gameScene = gameScene
super.init()
}
func layout() {}
func presentWithAnimation(animation:Animation) {
layout()
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: invert*gameScene.size.width, y: 0)
gameScene.addChild(self)
let action = SKAction.move(to: CGPoint(x: 0, y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.easeInEaseOut
self.run(action)
}
func dismissWithAnimation(animation:Animation) {
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: 0, y: 0)
let action = SKAction.move(to: CGPoint(x: invert*(-gameScene.size.width), y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.easeInEaseOut
self.run(action, completion: {self.removeFromParent()})
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MenuNode: CustomNode {
var label: SKLabelNode
var container: SKSpriteNode
override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Menu Scene")
label.horizontalAlignmentMode = .center
label.verticalAlignmentMode = .center
container = SKSpriteNode(color: UIColor.black, size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.gameScene.transitionToScene(sceneType: .Scores)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ScoresNode: CustomNode {
var label: SKLabelNode
var container: SKSpriteNode
override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Scores Scene")
label.horizontalAlignmentMode = .center
label.verticalAlignmentMode = .center
container = SKSpriteNode(color: UIColor.black, size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.gameScene.transitionToScene(sceneType: .Menu)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
enum SceneTransition{
case Menu, Scores
}
enum Animation {
case Left, Right, None
}
credit: Epic Byte
I have also tried using
viewWillTransitionToSize...
as this handles device orientation changes, I see that didChangeSize... is called multiple times in a device rotation therefore I prefer the viewWillTransitionToSize...
Thanks in advance.
Leon
Go into your Storyboard file, Select your ViewController's view and on the right hand bar, look for the slider image, it should be the 4th one from the left. This is called the attributes inspector. Change Content Mode to Center. This will give you black bars but stop the squishing.
This was the code created from the suggestion by #Knight0fDragon, producing SKLabelNodes that do not squash or stretch when rotated with scaleMode .resizeFill. Note the size of the view in this is for an iPad with 1024x768 resolution, creating a square skview of size 1024x1024. This would be easy to replicate with the largest value either width or height when the app loads depending on the orientation.
class GameViewController: UIViewController {
var scene : GameScene!
var newView: SKView!
override func viewDidLoad() {
super.viewDidLoad()
self.newView = SKView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 1024, height: 1024)))
(self.view as? SKView)?.contentMode = .center
self.newView.contentMode = .center
self.newView.autoresizesSubviews = false
self.newView.autoresizingMask = [.flexibleBottomMargin, .flexibleTopMargin, .flexibleLeftMargin, .flexibleRightMargin]
(self.view as? SKView)?.addSubview(newView)
self.newView.center = view.center
self.scene = GameScene(size: CGSize(width: 1024, height: 1024))
self.scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.scene.scaleMode = .resizeFill
self.newView.presentScene(scene)
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override var prefersStatusBarHidden: Bool {
return true
}
}
class GameScene: SKScene {
var labelNode: SKLabelNode!
var container: SKSpriteNode!
override func didMove(to view: SKView) {
self.backgroundColor = SKColor.darkGray
container = SKSpriteNode(color: .black, size: CGSize(width: 300, height: 300))
self.addChild(container)
labelNode = SKLabelNode(text: "hello")
labelNode.fontColor = .white
container.addChild(labelNode)
}
}

How to recognise which image was touched

I am developing an application which the user will be able to drag and drop items on a canvas and when he releases the image it is drawn on the canvas.
This is my DragImage class which handle the touches:
class DragImages: UIImageView {
var originalPos : CGPoint!
var dropTarget: UIView?
override init (frame : CGRect){
super.init(frame: frame)
}
required init?(coder aDecoder : NSCoder){
super.init(coder : aDecoder)
}
override func touchesBegan(_ touches : Set<UITouch>,with event: UIEvent?){
originalPos = self.center
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let position = touch.location(in: self.superview)
self.center = CGPoint(x : position.x, y : position.y)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, let target = dropTarget{
let position = touch.location(in: self.superview)
if target.frame.contains(position){
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "onTargetDropped"), object: nil))
}else {
self.center = originalPos
}
}
print(self.center.x, self.center.y)
self.center = originalPos
}
func getEndPosX() -> CGFloat{
return self.center.x
}
func getEndPosY() -> CGFloat {
return self.center.y
}
}
In my ViewController class I added this piece of code to handle the touches etc:
ornament1.dropTarget = xmasTree
ornament2.dropTarget = xmasTree
ornament3.dropTarget = xmasTree
ornament4.dropTarget = xmasTree
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.itemDroppedOnTree(_:)), name: NSNotification.Name(rawValue: "onTargetDropped"), object: nil)
}
func itemDroppedOnTree(_ notif : AnyObject){
}
I managed to get the X and Y position when the image is dragged on the canvas but i cant find a way to recognise which of the 4 images is being dropped in order for me to draw that specific one!
You could add the sender to your notification (and also the position):
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "onTargetDropped"), object: self, userInfo: ["position":position]))
and get it later in itemDroppedOnTree:
func itemDroppedOnTree(_ notif : NSNotification){
let position = notif.userInfo["position"]
let sender = notif.object as! DragImage
if sender === dragImage1 {
//...
} else if sender === dragImage2 {
//...
}
}
I recommend against it though and plead to use a delegate to inform the ViewController instead. (Opinion based: In general, use Notifications for to-many broadcasts only.)
The delegate function should have the sender as first parameter. According to func tableView: tableView:UITableView, cellForRowAt indexPath:IndexPath).
This way you know which image is sending its new position and can compare it to your property like in the above example:
if dragImage === dragImage1 {...
Your code plus working delegate to paste to Playground:
import UIKit
import PlaygroundSupport
protocol DragImageDelegate: class {
func dragimage(_ dragImage:DragImage, didDropAt position:CGPoint)
}
class DragImage: UIImageView {
weak var delegate: DragImageDelegate?
var originalPos : CGPoint!
var dropTarget: UIView?
override init (frame : CGRect) {
super.init(frame: frame)
isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches : Set<UITouch>,with event: UIEvent?){
originalPos = self.center
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let position = touch.location(in: self.superview)
self.center = CGPoint(x : position.x, y : position.y)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, let target = dropTarget {
let position = touch.location(in: self.superview)
if target.frame.contains(position){
print(self.center.x, self.center.y)
guard let delegate = self.delegate else {
print("delegate not set")
return
}
print(self.center.x, self.center.y)
delegate.dragimage(self, didDropAt: position)
return
}
}
self.center = originalPos
}
}
class MyVC: UIViewController, DragImageDelegate {
let dragImage1 = DragImage(frame: CGRect(x: 0.0, y: 0.0, width: 30.0, height: 30.0))
let dragImage2 = DragImage(frame: CGRect(x: 0.0, y: 100.0, width: 30.0, height: 30.0))
override func viewDidLoad() {
let target = UIView(frame: CGRect(x: 200.0, y: 400.0, width: 30.0, height: 30.0))
target.backgroundColor = .black
view.addSubview(target)
dragImage1.backgroundColor = .white
dragImage2.backgroundColor = .white
dragImage1.dropTarget = target
dragImage2.dropTarget = target
view.addSubview(dragImage1)
view.addSubview(dragImage2)
dragImage1.delegate = self
dragImage2.delegate = self
}
private func move(_ view:UIView, to position:CGPoint) {
view.frame = CGRect(x: position.x, y: position.y, width: view.frame.size.width, height: view.frame.size.height)
}
// MARK: - DragImageDelegate
func dragimage(_ dragImage: DragImage, didDropAt position: CGPoint) {
if dragImage === dragImage1 {
move(dragImage1, to: position)
} else if dragImage === dragImage2 {
move(dragImage2, to: position)
}
}
}
var container = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 300.0, height: 600.0))
let myVc = MyVC()
myVc.view.frame = CGRect(x: 0.0, y: 0.0, width: 300.0, height: 600.0)
myVc.view.backgroundColor = .green
container.addSubview(myVc.view)
PlaygroundPage.current.liveView = container
Result:

How to call a function that is defined in a different view controller?

I'm trying to call the function called "kick". Inside it is a CAAnimation that I am playing on this man I made. So usually I would just call the function in the same view controller as it was made in. But because I have a HUD/overlay scene the displays over my 3D game. And the HUD/overlayscene is made in a different view controller specifically just to set up the HUD I can't get the HUD view controller to Recognize any functions from the Main View Controller.
MainViewController:
Code:
class GameViewController: UIViewController, ADBannerViewDelegate, SKPhysicsContactDelegate, SKSceneDelegate, SCNSceneRendererDelegate, SCNPhysicsContactDelegate{
var HUDView: HUD!
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
//-----Scene-Setup----------
let scnView = self.view as! SCNView
scnView.scene = FieldScene
scnView.playing = true
scnView.loops = true
self.HUDView = HUD(size: CGSizeMake(100, 100))
scnView.overlaySKScene = self.HUDView
scnView.delegate = self
scnView.overlaySKScene!.delegate = self
scnView.overlaySKScene!.userInteractionEnabled = true
scnView.backgroundColor = UIColor.whiteColor()
scnView.allowsCameraControl = true
scnView.showsStatistics = false
let GuyScene = SCNScene(named: "art.scnassets/The1.dae")
let Guy: SCNNode = GuyScene!.rootNode.childNodeWithName("Armature", recursively: true)!
let GuyBody: SCNNode = GuyScene!.rootNode.childNodeWithName("Cube", recursively: true)!
//----Giveing it a physics---------
let collisionCapsuleRadius2 = CGFloat(0.1)
let collisionCapsuleHeight2 = CGFloat(0.1)
Guy.position = SCNVector3(x: -30.0, y: 30.0, z: 0.0)
Guy.scale = SCNVector3Make(50, 50, 50)
Guy.rotation = SCNVector4Make(0, 1, 0, 1 )
Guy.physicsBody = SCNPhysicsBody(type: .Dynamic, shape:SCNPhysicsShape(geometry: SCNCapsule(capRadius: collisionCapsuleRadius2, height: collisionCapsuleHeight2), options:nil))
Guy.physicsBody?.affectedByGravity = true
Guy.physicsBody?.friction = 0 //
Guy.physicsBody?.restitution = 1 //bounceness of the object
Guy.physicsBody?.angularDamping = 1 // rotationess
Guy.physicsBody?.mass = 1
Guy.physicsBody?.rollingFriction = 0
scnView.scene!.rootNode.addChildNode(Guy)
scnView.scene!.rootNode.addChildNode(GuyBody)
}
//Ok Kick function gets declared right here
func Kick() {
//-----Animate-Guy-----Working-Perfectly-----------
let KickAnimation = CAAnimation.animationWithSceneNamed("art.scnassets/The1Anima.dae")!
Guy.addAnimation(KickAnimation, forKey: "Go")
Guy.removeAnimationForKey("Go", fadeOutDuration: 3.0)
}
}
extension CAAnimation {
class func animationWithSceneNamed(name: String) -> CAAnimation? {
var animation: CAAnimation?
if let scene = SCNScene(named: name) {
scene.rootNode.enumerateChildNodesUsingBlock({ (child, stop) in
if child.animationKeys.count > 0 {
animation = child.animationForKey(child.animationKeys.first!)
stop.initialize(true)
}
})
}
return animation
}
}
//Ok Below is my HUD/Overlayscene which holds the button that i would like call the function in.
extension GameViewController {
// was never used
}
class HUD: SKScene {
//-----------------Controller-Buttons----------------------
var ButtonA = SKSpriteNode(imageNamed:"EnhancedAButton")
override init(size: CGSize) {
super.init(size: size)
//----A-Button--Creation -------------------
ButtonA.size = CGSize(width: 7, height: 11)
ButtonA.anchorPoint = CGPointMake(-11.35, -0.6)
ButtonA.zPosition = 0
ButtonA.alpha = 0.45
self.addChild(ButtonA)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
//Ok here is the function I need to put "Kick()" inside it but when I do it doenst recognize it because the function wasnt declared in this view controller
func AButtonPressed() {
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
for touch: AnyObject in touches {
let location1 = touch.locationInNode(self)
if self.nodeAtPoint(location1) == self.ButtonA {
AButtonPressed()
print("AButtonPressed")
}
}
}
}
enter image description here
enter image description here
This is practically the best way to handle this. Other methods are messy while this is the most decoupled natural way to handle it. If you have any questions, let me know.
extension GameViewController: GameDelegate {
func Kick() {
//-----Animate-Guy-----Working-Perfectly-----------
let KickAnimation = CAAnimation.animationWithSceneNamed("art.scnassets/The1Anima.dae")!
Guy.addAnimation(KickAnimation, forKey: "Go")
Guy.removeAnimationForKey("Go", fadeOutDuration: 3.0)
}
}
protocol GameDelegate {
func Kick()
}
class HUD: SKScene {
var delegate: GameDelegate?
override init(size: CGSize) {
super.init(size: size)
//this calls Kick anywhere in the HUD class
delegate?.Kick()
//----A-Button--Creation -------------------
ButtonA.size = CGSize(width: 7, height: 11)
ButtonA.anchorPoint = CGPointMake(-11.35, -0.6)
ButtonA.zPosition = 0
ButtonA.alpha = 0.45
self.addChild(ButtonA)
}
}
after the line that is inside of GameViewController's viewDidLoad()
//below this line
self.HUDView = HUD(size: CGSizeMake(100, 100))
//add in
self.HUDView.delegate = self
After all of this, is added in, you can delete the copy of Kick that is not in the extension. Both controllers can access the function.

Draggable UITextView

I need to create a draggable UITextView.
I managed to create a draggable view and I tried to add a TextView as a subview but it didn't work, the TextView wasn't editable and it didn't appear inside the view but below it.
This is the custom view code I created:
class DraggableView: UIView {
var lastLocation:CGPoint = CGPointMake(0, 0)
override init(frame: CGRect) {
super.init(frame: frame)
// Initialization code
var panRecognizer = UIPanGestureRecognizer(target:self, action:"detectPan:")
self.gestureRecognizers = [panRecognizer]
self.backgroundColor = UIColor.redColor()
//add UITextView
var text = UITextView()
text.backgroundColor = UIColor.brownColor()
text.text = "This is a test"
text.frame = CGRectMake(self.center.x, self.center.y, 40, 40)
self.addSubview(text)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func detectPan(recognizer:UIPanGestureRecognizer) {
var translation = recognizer.translationInView(self.superview!)
self.center = CGPointMake(lastLocation.x + translation.x, lastLocation.y + translation.y)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
// Promote the touched view
//self.superview?.bringSubviewToFront(self)
// Remember original location
lastLocation = self.center
}
}
How can I correctly add a TextView to a draggable view?
Try this code , I updated your detectPan method, may u need to adjust as per your requirement
class DraggableView: UIView {
var lastLocation:CGPoint = CGPointMake(0, 0)
var startFrame:CGRect!
override init(frame: CGRect) {
super.init(frame: frame)
// Initialization code
var panRecognizer = UIPanGestureRecognizer(target:self, action:"detectPan:")
self.gestureRecognizers = [panRecognizer]
self.backgroundColor = UIColor.redColor()
//add UITextView
var text = UITextView()
text.backgroundColor = UIColor.brownColor()
text.text = "This is a test"
text.frame = CGRectMake(self.center.x, self.center.y, 40, 40)
self.addSubview(text)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func detectPan(gesture:UIPanGestureRecognizer) {
switch(gesture.state)
{
case .Began:
self.startFrame = gesture.view?.frame
case .Changed:
let newCoord = gesture.locationInView(gesture.view)
let dX = newCoord.x
let dY = newCoord.y
let center = CGPointMake((gesture.view?.frame.origin.x)! + dX, (gesture.view?.frame.origin.y)! + dY)
gesture.view?.center = center
case .Ended:
gesture.view?.frame = self.startFrame
print("ended")
default:
print("will not handel")
}
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
// Promote the touched view
//self.superview?.bringSubviewToFront(self)
// Remember original location
lastLocation = self.center
}
}

Resources