SpriteKit - safe area layout Iphone X - ios

So, Currently I have a game that i’ve made in sprite kit and have used this way of fitting everything to the screen size:
buyButton = SKSpriteNode(texture: SKTexture(imageNamed: "BuyButton"), color: .clear, size: CGSize(width: frame.maxX / 2.9, height: frame.maxY / 10))
buyButton.position = CGPoint(x:-frame.maxX + frame.midX*2, y: -frame.maxY + frame.midY*1.655)
addChild(buyButton)
as you can see it uses the frame to calculate the width and height along with the position of the node on the scene, this works with all screen sizes that I have been using from the 6s to the 8 Plus.
but when it comes to the iPhone X there is a problem with it. it seems to stretch everything because of the screen size being bigger and oddly shaped as you can see below compared to the iPhone 8 Plus
I’ve been looking for solutions to this problem but none have really helped or just even make it worse and I couldn’t understand how to programmatically use the safe area layout in my sprite kit project like in this solution here
My Question is
How do I go about getting everything to fit in the iPhone X’s screen so that it fits and isn’t cutting off the score labels and stuff I have in the top right corners?
EDIT 2:
itemDescriptionLabel = UILabel(frame: CGRect(x: frame.maxX - 150, y: frame.maxY - 130 , width: frame.maxX / 0.7, height: frame.maxY / 3.5))
itemDescriptionLabel.font = UIFont(name: "SFCompactRounded-Light", size: 17)
itemDescriptionLabel.text = "This purchase stops all the popup ads that happen after a certain amount of time playing the game from showing up."
itemDescriptionLabel.numberOfLines = 4
itemDescriptionLabel.adjustsFontSizeToFitWidth = true
self.scene?.view?.addSubview(itemDescriptionLabel)
EDIT 3
EDIT 4
let safeAreaInsets = yourSpriteKitView.safeAreaInsets;
buyButton.position = CGPoint(x:safeAreaInsets.left - frame.maxX + frame.midX*2, y: safeAreaInsets.top - frame.maxY + frame.midY*1.655)
EDIT 5
screenWidth = self.view!.bounds.width
screenHeight = self.view!.bounds.height
coinScoreLabel = Score(num: 0,color: UIColor(red:1.0, green: 1.0, blue: 0.0, alpha: 1), size: 50, useFont: "SFCompactRounded-Heavy" ) // UIColor(red:1.00, green:0.81, blue:0.07, alpha:1.0)
coinScoreLabel.name = "coinScoreLabel"
coinScoreLabel.horizontalAlignmentMode = .right
//coinScoreLabel.verticalAlignmentMode = .top
coinScoreLabel.position = CGPoint(x: screenWidth / 2.02, y: inset.top - screenHeight / 2.02)
coinScoreLabel.zPosition = 750
addChild(coinScoreLabel)
I tried it on another SpriteNode that I have such as this one below, which it worked for I have no idea why the yellow label is doing this.
playButton = SKSpriteNode(texture: SKTexture(imageNamed: "GameOverMenuPlayButton"), color: .clear, size: CGSize(width: 120, height: 65))
playButton.position = CGPoint(x: inset.right - frame.midX, y: inset.bottom + frame.minY + playButton.size.height / 1.7)
playButton.zPosition = 1000
deathMenuNode.addChild(playButton)
It came out like this which is perfect:

Interesting question. The problem borns when we try to "transform" our view to SKView to create our game scene. Essentially, we could intercept that moment (using viewWillLayoutSubviews instead of viewDidLoad) and transform the view frame accordling with the safeAreaLayoutGuide layout frame property.
Some code as example:
GameViewController:
class GameViewController: UIViewController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if #available(iOS 11.0, *), let view = self.view {
view.frame = self.view.safeAreaLayoutGuide.layoutFrame
}
guard let view = self.view as! SKView? else { return }
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
view.showsPhysics = true
view.showsDrawCount = true
let scene = GameScene(size:view.bounds.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
override func viewDidLoad() {
super.viewDidLoad()
print("---")
print("∙ \(type(of: self))")
print("---")
}
}
GameScene:
class GameScene: SKScene {
override func didMove(to view: SKView) {
print("---")
print("∙ \(type(of: self))")
print("---")
let labLeftTop = SKLabelNode.init(text: "LEFTTOP")
labLeftTop.horizontalAlignmentMode = .left
labLeftTop.verticalAlignmentMode = .top
let labRightTop = SKLabelNode.init(text: "RIGHTTOP")
labRightTop.horizontalAlignmentMode = .right
labRightTop.verticalAlignmentMode = .top
let labLeftBottom = SKLabelNode.init(text: "LEFTBTM")
labLeftBottom.horizontalAlignmentMode = .left
labLeftBottom.verticalAlignmentMode = .bottom
let labRightBottom = SKLabelNode.init(text: "RIGHTBTM")
labRightBottom.horizontalAlignmentMode = .right
labRightBottom.verticalAlignmentMode = .bottom
self.addChild(labLeftTop)
self.addChild(labRightTop)
self.addChild(labLeftBottom)
self.addChild(labRightBottom)
labLeftTop.position = CGPoint(x:0,y:self.frame.height)
labRightTop.position = CGPoint(x:self.frame.width,y:self.frame.height)
labLeftBottom.position = CGPoint(x:0,y:0)
labRightBottom.position = CGPoint(x:self.frame.width,y:0)
}
}
Other way to do:
Another way to obtain just only the differences between the safe area layout frame and the current view frame without passing through viewWillLayoutSubviews for launching our scene, could be using protocols.
P.S.: Down below, I've report a reference image where you can see the different sizes between:
the main screen height (our view height)
safe area
status bar height (on the top, 44 px for the iPhone X)
GameViewController:
protocol LayoutSubviewDelegate: class {
func safeAreaUpdated()
}
class GameViewController: UIViewController {
weak var layoutSubviewDelegate:LayoutSubviewDelegate?
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let _ = self.view {
layoutSubviewDelegate?.safeAreaUpdated()
}
}
override func viewDidLoad() {
super.viewDidLoad()
print("---")
print("∙ \(type(of: self))")
print("---")
guard let view = self.view as! SKView? else { return }
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
view.showsPhysics = true
view.showsDrawCount = true
let scene = GameScene(size:view.bounds.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
}
GameScene:
class GameScene: SKScene,LayoutSubviewDelegate {
var labLeftTop:SKLabelNode!
var labRightTop:SKLabelNode!
var labLeftBottom:SKLabelNode!
var labRightBottom:SKLabelNode!
func safeAreaUpdated() {
if let view = self.view {
let top = view.bounds.height-(view.bounds.height-view.safeAreaLayoutGuide.layoutFrame.height)+UIApplication.shared.statusBarFrame.height
let bottom = view.bounds.height - view.safeAreaLayoutGuide.layoutFrame.height-UIApplication.shared.statusBarFrame.height
refreshPositions(top: top, bottom: bottom)
}
}
override func didMove(to view: SKView) {
print("---")
print("∙ \(type(of: self))")
print("---")
if let view = self.view, let controller = view.next, controller is GameViewController {
(controller as! GameViewController).layoutSubviewDelegate = self
}
labLeftTop = SKLabelNode.init(text: "LEFTTOP")
labLeftTop.horizontalAlignmentMode = .left
labLeftTop.verticalAlignmentMode = .top
labRightTop = SKLabelNode.init(text: "RIGHTTOP")
labRightTop.horizontalAlignmentMode = .right
labRightTop.verticalAlignmentMode = .top
labLeftBottom = SKLabelNode.init(text: "LEFTBTM")
labLeftBottom.horizontalAlignmentMode = .left
labLeftBottom.verticalAlignmentMode = .bottom
labRightBottom = SKLabelNode.init(text: "RIGHTBTM")
labRightBottom.horizontalAlignmentMode = .right
labRightBottom.verticalAlignmentMode = .bottom
self.addChild(labLeftTop)
self.addChild(labRightTop)
self.addChild(labLeftBottom)
self.addChild(labRightBottom)
labLeftTop.position = CGPoint(x:0,y:self.frame.height)
labRightTop.position = CGPoint(x:self.frame.width,y:self.frame.height)
labLeftBottom.position = CGPoint(x:0,y:0)
labRightBottom.position = CGPoint(x:self.frame.width,y:0)
}
func refreshPositions(top:CGFloat,bottom:CGFloat){
labLeftTop.position = CGPoint(x:0,y:top)
labRightTop.position = CGPoint(x:self.frame.width,y:top)
labLeftBottom.position = CGPoint(x:0,y:bottom)
labRightBottom.position = CGPoint(x:self.frame.width,y:bottom)
}
}
Output:
Subclassing methods:
Another way to get the correct safe area dimensions without touch your current classes code, could be made using some subclass, so for our GameViewController we set it as GameController and for GameScene we can set it as GenericScene like this example:
GameViewController:
class GameViewController: GameController {
override func viewDidLoad() {
super.viewDidLoad()
guard let view = self.view as! SKView? else { return }
view.ignoresSiblingOrder = true
let scene = GameScene(size:view.bounds.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
}
protocol LayoutSubviewDelegate: class {
func safeAreaUpdated()
}
class GameController:UIViewController {
weak var layoutSubviewDelegate:LayoutSubviewDelegate?
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let _ = self.view {
layoutSubviewDelegate?.safeAreaUpdated()
}
}
}
GameScene:
class GameScene: GenericScene {
override func safeAreaUpdated() {
super.safeAreaUpdated()
if let view = self.view {
let insets = view.safeAreaInsets
// launch your code to update positions here
}
}
override func didMove(to view: SKView) {
super.didMove(to: view)
// your stuff
}
}
class GenericScene: SKScene, LayoutSubviewDelegate {
func safeAreaUpdated(){}
override func didMove(to view: SKView) {
if let view = self.view, let controller = view.next, controller is GameViewController {
(controller as! GameViewController).layoutSubviewDelegate = self
}
}
}
References:

You can get margins through your view's safeAreaInsets. The insets will be zero on most devices, but non-zero on the iPhone X.
For example replace your line with this:
let safeAreaInsets = yourSpriteKitView.safeAreaInsets;
buyButton.position = CGPoint(x:safeAreaInsets.left - frame.maxX + frame.midX*2, y: safeAreaInsets.top - frame.maxY + frame.midY*1.655)

Related

How to attach multiple UIDynamicItems to each other

I am trying to implement circles attached to each other like in Apple's Music App via UIDynamicAnimator. I need to attach circles to each other and to view center. I was trying to implement this via UIAttachmentBehavior, but seems to it's not supporting multiple attachments. In result, circles overlaps on each other :)
let attachment = UIAttachmentBehavior(item: circle, attachedToAnchor: CGPoint(x: view.center.x, y: view.center.y))
attachment.length = 10
animator?.addBehavior(attachment)
let push = UIPushBehavior(items: [circle], mode: .continuous)
collision.addItem(circle)
animator?.addBehavior(push)
What I am doing wrong?
I don't think the apple music genre picker thing uses UIAttachmentBehavior which is closer to attaching two views with a pole or a rope. But, it seems like the problem you're experiencing might be that all of the views are added at the same location which has the effect of placing them on top of each other and with the collision behavior causes them to be essentially be stuck together. One thing to do is to turn on UIDynamicAnimator debugging by calling animator.setValue(true, forKey: "debugEnabled").
For recreating the above circle picker design, I would look into using UIFieldBehavior.springField().
For example:
class ViewController: UIViewController {
lazy var animator: UIDynamicAnimator = {
let animator = UIDynamicAnimator(referenceView: view)
return animator
}()
lazy var collision: UICollisionBehavior = {
let collision = UICollisionBehavior()
collision.collisionMode = .items
return collision
}()
lazy var behavior: UIDynamicItemBehavior = {
let behavior = UIDynamicItemBehavior()
behavior.allowsRotation = false
behavior.elasticity = 0.5
behavior.resistance = 5.0
behavior.density = 0.01
return behavior
}()
lazy var gravity: UIFieldBehavior = {
let gravity = UIFieldBehavior.springField()
gravity.strength = 0.008
return gravity
}()
lazy var panGesture: UIPanGestureRecognizer = {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.didPan(_:)))
return panGesture
}()
var snaps = [UISnapBehavior]()
var circles = [CircleView]()
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(panGesture)
animator.setValue(true, forKey: "debugEnabled")
addCircles()
addBehaviors()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
gravity.position = view.center
snaps.forEach {
$0.snapPoint = view.center
}
}
func addCircles() {
(1...30).forEach { index in
let xIndex = index % 2
let yIndex: Int = index / 3
let circle = CircleView(frame: CGRect(origin: CGPoint(x: xIndex == 0 ? CGFloat.random(in: (-300.0 ... -100)) : CGFloat.random(in: (500 ... 800)), y: CGFloat(yIndex) * 200.0), size: CGSize(width: 100, height: 100)))
circle.backgroundColor = .red
circle.text = "\(index)"
circle.textAlignment = .center
view.addSubview(circle)
gravity.addItem(circle)
collision.addItem(circle)
behavior.addItem(circle)
circles.append(circle)
}
}
func addBehaviors() {
animator.addBehavior(collision)
animator.addBehavior(behavior)
animator.addBehavior(gravity)
}
#objc
private func didPan(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: sender.view)
switch sender.state {
case .began:
animator.removeAllBehaviors()
fallthrough
case .changed:
circles.forEach { $0.center = CGPoint(x: $0.center.x + translation.x, y: $0.center.y + translation.y)}
case .possible, .cancelled, .failed:
break
case .ended:
circles.forEach { $0.center = CGPoint(x: $0.center.x + translation.x, y: $0.center.y + translation.y)}
addBehaviors()
#unknown default:
break
}
sender.setTranslation(.zero, in: sender.view)
}
}
final class CircleView: UILabel {
override var collisionBoundsType: UIDynamicItemCollisionBoundsType {
return .ellipse
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.height * 0.5
layer.masksToBounds = true
}
}
For more information I would watch What's New in UIKit Dynamics and Visual Effects from WWDC 2015

How do you adapt your nodes position to landscape & portrait

Im trying to make a game board that is universal and can be played on ipad/iphones either in portrait or landscape. The tiles looks fine in portrait but when i switch to landscape it gets cut off. How can i make sure the board will always be centered?
override func didMove(to view: SKView) {
let numRows = 7
let numCols = 7
var counter = 0
let squareSize = CGSize(width: view.bounds.width/7, height: view.bounds.width/7)
for row in 0..<numRows{
for col in 0..<numCols {
if counter == 49 {
break
}
let spriteNode = SKSpriteNode(imageNamed: "piece\(counter)")
spriteNode.size = squareSize
spriteNode.position = CGPoint(x: 0-(frame.width*0.43)+(CGFloat(col)*spriteNode.size.width), y: -CGFloat(row)*spriteNode.size.height + frame.height/3 )
addChild(spriteNode)
counter = counter+1
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: "GameScene") {
scene.scaleMode = .resizeFill
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
It looks like this:
I would change the Scene scale mode from scene.scaleMode = .resizeFill to scene.scaleMode = .aspectFit. In a game the SKView frame size is not required and you can work within your own coordinate space (size property of the scene) e.g. set to 100 x 100. -> Interface Builder setting.
Don't relate anything on the view frame/bounds of the SKView. The code from your scene controller should work then as below, all related to the size of the scene.
override func didMove(to view: SKView) {
let numRows = 7
let numCols = 7
var counter = 0
let squareSize = CGSize(width: size.width/7, height: size.width/7)
for row in 0..<numRows{
for col in 0..<numCols {
if counter == 49 {
break
}
let spriteNode = SKSpriteNode(imageNamed: "piece\(counter)")
spriteNode.size = squareSize
spriteNode.position = CGPoint(x: 0-(size.width*0.43)+(CGFloat(col)*spriteNode.size.width), y: -CGFloat(row)*spriteNode.size.height + size.height/3 )
addChild(spriteNode)
counter = counter+1
}
}
}

MKMapView covering SKSpriteNodes

I am experimenting with using MapKit and SpriteKit together, but I have hit a pretty major wall.
I have my map set up in my GameScene because I don't want the scene covering it, however when I attempt to add a SpriteNode to the view, it doesn't show. Upon reducing the map's alpha, I can see that the sprites are being rendered underneath the map.
Changing zPosition hasn't helped, as I'm assuming views such as MKMapViews are on a separate layer from SpriteKit objects.
Essentially what I am trying to accomplish is putting SpriteKit elements over top of my MKMapView (if possible, without storyboards - I am trying to do everything programmatically). I have spent hours trying to find an answer on the Internet but was unsuccessful.
Here is my GameScene:
import UIKit
import SpriteKit
import GameplayKit
import MapKit
import CoreLocation
class GameScene: SKScene, MKMapViewDelegate, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var map = MKMapView()
let healthBar = SKSpriteNode(imageNamed: "Square")
let maxHealthBar = SKSpriteNode(imageNamed: "Square")
var UID = String()
var ref = DatabaseReference()
static var username = String()
static var health = Int()
static var maxHealth = Int()
var width = CGFloat()
var right = CGFloat()
var left = CGFloat()
var top = CGFloat()
var bot = CGFloat()
var margin = CGFloat()
var middleX = CGFloat()
var middleY = CGFloat()
var maxBarWidth = CGFloat()
override func didMove(to view: SKView) {
map.frame = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
map.delegate = self
map.showsScale = false
map.showsPointsOfInterest = false
map.showsUserLocation = true
map.showsBuildings = true
map.isZoomEnabled = false
map.isScrollEnabled = false
map.isPitchEnabled = false
map.isRotateEnabled = false
let region = MKCoordinateRegionMakeWithDistance((locationManager.location?.coordinate)!, 400, 400)
map.setRegion(region, animated: false)
print("region:", map.region)
locationManager.requestWhenInUseAuthorization()
if (CLLocationManager.locationServicesEnabled()) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
view.addSubview(map)
width = frame.width
right = frame.width
left = frame.width - frame.width
top = frame.height
bot = frame.height - frame.height
margin = frame.width / 75
middleX = frame.width / 2
middleY = frame.height / 2
maxBarWidth = width - margin * 2
let maxBarHeight = CGFloat(30)
let healthBarWidth = width - margin * 2
let barHeight = maxBarHeight * 0.75
maxHealthBar.anchorPoint = CGPoint(x: 0.0, y: 0.5)
maxHealthBar.position = CGPoint(x: middleX, y: top - margin - maxBarHeight / 2)
maxHealthBar.size = CGSize(width: maxBarWidth, height: maxBarHeight)
maxHealthBar.color = UIColor.black
maxHealthBar.colorBlendFactor = 1.0
maxHealthBar.zPosition = 1.1
self.addChild(maxHealthBar)
healthBar.anchorPoint = CGPoint(x: 0.0, y: 0.5)
healthBar.position = CGPoint(x: middleX - healthBarWidth / 2, y: maxHealthBar.position.y)
healthBar.size = CGSize(width: healthBarWidth, height: barHeight)
healthBar.color = UIColor.red
healthBar.colorBlendFactor = 1.0
healthBar.zPosition = 1000000
self.addChild(healthBar)
print("parent", healthBar.parent)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
takeDamage(damage: 10)
}
}
func takeDamage(damage: Int) {
GameScene.health -= damage
updateHealth()
}
func updateHealth() {
let percentFull = CGFloat(GameScene.health / GameScene.maxHealth)
let healthWidth = maxBarWidth * percentFull
healthBar.size.width = healthWidth
}
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
map.setCenter((locationManager.location?.coordinate)!, animated: true)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
And here is my GameViewController:
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if (SignInScene.signInLoaded == false) {
if let scene = GameScene(fileNamed: "GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.size = skView.bounds.size
scene.scaleMode = .aspectFill
skView.presentScene(scene)
}
}
}
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .portrait
} else {
return .portrait
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override var prefersStatusBarHidden: Bool {
return false
}
}
I am extremely stuck on this one and would greatly appreciate any help!
Thanks in advance!
Your problem is your UIView heirarchy in your view controller,
Here is what your project looks like:
Controller
--SKView
----SKScene
--MKMapView
You need to change your hierarchy to be
Controller
--MKMapView
--SKView
----SKSceneView
I would recommend doing this inside your storyboard, so that you can keep design and code separated.
After you have changed your hierarchy, you need to do the following in your GameScene:
view.allowsTransparency = true
view.backgroundColor = .clear
backgroundColor = .clear
This will give you a view and scene that has no background color, which means that your map will show underneath it.
Now if you want touch events to hit the MKMapView and not the SKView, then you need to disable userInteractionEnabled on your MKMapView, or pass the touch events to the map from your SKView.

Zoom on UIView contained in UIScrollView

I have some trouble handling zoom on UIScrollView that contains many subviews. I already saw many answers on SO, but none of them helped me.
So, basically what I'm doing is simple : I have a class called UIFreeView :
import UIKit
class UIFreeView: UIView, UIScrollViewDelegate {
var mainUIView: UIView!
var scrollView: UIScrollView!
var arrayOfView: [UIView]?
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func setupView() {
// MainUiView
self.mainUIView = UIView(frame: self.frame)
self.addSubview(mainUIView)
// ScrollView
self.scrollView = UIScrollView(frame: self.frame)
self.scrollView.delegate = self
self.addSubview(self.scrollView)
}
func reloadViews(postArray:[Post]?) {
if let postArray = postArray {
print("UIFreeView::reloadVIew.postArraySize = \(postArray.count)")
let size: CGFloat = 80.0
let margin: CGFloat = 20.0
scrollView.contentSize.width = (size * CGFloat(postArray.count))+(margin*CGFloat(postArray.count))
scrollView.contentSize.height = (size * CGFloat(postArray.count))+(margin*CGFloat(postArray.count))
for item in postArray {
let view = buildPostView(item)
self.scrollView.addSubview(view)
}
}
}
fileprivate func buildPostView(_ item:Post) -> UIView {
// Const
let size: CGFloat = 80.0
let margin: CGFloat = 5.0
// Var
let view = UIView()
let textView = UITextView()
let backgroundImageView = UIImageView()
// Setup view
let x = CGFloat(UInt64.random(lower: UInt64(0), upper: UInt64(self.scrollView.contentSize.width)))
let y = CGFloat(UInt64.random(lower: UInt64(0), upper: UInt64(self.scrollView.contentSize.height)))
view.frame = CGRect(x: x,
y: y,
width: size,
height: size)
// Setup background view
backgroundImageView.frame = CGRect(x: 0,
y: 0,
width: view.frame.size.width,
height: view.frame.size.height)
var bgName = ""
if (item.isFromCurrentUser) {
bgName = "post-it-orange"
} else {
bgName = "post-it-white"
}
backgroundImageView.contentMode = .scaleAspectFit
backgroundImageView.image = UIImage(named: bgName)
view.addSubview(backgroundImageView)
// Setup text view
textView.frame = CGRect(x: margin,
y: margin,
width: view.frame.size.width - margin*2,
height: view.frame.size.height - margin*2)
textView.backgroundColor = UIColor.clear
textView.text = item.content
textView.isEditable = false
textView.isSelectable = false
textView.isUserInteractionEnabled = false
view.addSubview(textView)
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
view.addGestureRecognizer(gestureRecognizer)
return view
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.scrollView
}
func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.scrollView)
// note: 'view' is optional and need to be unwrapped
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.scrollView)
}
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
This class is working perfectly, I can scroll through all my views, but I can't zoom-in and zoom-out in order to see less/more views on my screen.
I think the solution is simple, but I can't seem to find a real solution for my problem ..
Thanks !

Swift Collision Boundary not working

Swift 2.0, xcode 7, Ios 9
Intention: I want the two squares to drop to the bottom and stay at the bottom of the screen.
What is happening now: There is no error preventing the code from running, the gravity works fine but the collision just seems to be ignored. Thus resulting in the two objects falling through the bottom of the screen.
Note: the subclass is UIView not UIViewController and this view is accessed from a segue.
Many Thanks!
Code :
import UIKit
class graphs: UIView {
//create two shapes
var greenSquare: UIView?
var redSquare: UIView?
var animator: UIDynamicAnimator?
override func drawRect(rect: CGRect) {
var dimen = CGRectMake(25, 25, 60, 60)
greenSquare = UIView(frame: dimen)
greenSquare?.backgroundColor = UIColor.greenColor()
dimen = CGRectMake(130, 25, 90, 90)
redSquare = UIView(frame: dimen)
redSquare?.backgroundColor = UIColor.redColor()
//add them to the screen
self.addSubview(greenSquare!)
self.addSubview(redSquare!)
}
#IBAction func startbutton(sender: AnyObject) {
//initialise the animator
animator = UIDynamicAnimator()
//colision
let boundries = UICollisionBehavior(items:[greenSquare!,redSquare!])
boundries.translatesReferenceBoundsIntoBoundary = true
//add gravity
let gravity = UIGravityBehavior(items: [greenSquare!, redSquare!])
let direction = CGVectorMake(0.0, 1.0)
gravity.gravityDirection = direction
animator?.addBehavior(boundries)
animator?.addBehavior(gravity)
}
}
I fixed it by changing the subclass to a UIViewController
why did the previous code not work?
this is the new code:
import UIKit
class DrawView: UIViewController {
//Create two shapes
var greenSquare: UIView?
var redSquare: UIView?
var animator: UIDynamicAnimator?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func startbutton(sender: UIButton) {
//Create the shapes
var dimen = CGRectMake(25, 25, 60, 60)
greenSquare = UIView(frame: dimen)
greenSquare?.backgroundColor = UIColor.greenColor()
dimen = CGRectMake(130, 25, 90, 90)
redSquare = UIView(frame: dimen)
redSquare?.backgroundColor = UIColor.redColor()
//Add them to the screen
self.view.addSubview(greenSquare!)
self.view.addSubview(redSquare!)
//Initialize the animator
animator = UIDynamicAnimator(referenceView: self.view)
//Gravity
let gravity = UIGravityBehavior(items: [greenSquare!, redSquare!] )
let direction = CGVectorMake(0.0, 1.0)
gravity.gravityDirection = direction
//Collision
let boundries = UICollisionBehavior(items: [greenSquare!, redSquare!] )
boundries.translatesReferenceBoundsIntoBoundary = true
//Elasticity
let bounce = UIDynamicItemBehavior(items: [greenSquare!, redSquare!] )
bounce.elasticity = 0.5
animator?.addBehavior(bounce)
animator?.addBehavior(boundries)
animator?.addBehavior(gravity)
}
}

Resources