I have a situation where I have to reposition the map box compass view to a different location. The compassView is now rotating when I rotate the map taking some other point as its(compassView) axis and gives me a weird outcome. Screenshot is attached, the black mapBox default compass icon is rotating, refer the screenshots. Is this a MapBox sdk bug? if so, are there any work around? And tweaks? I am confused. Expert advices needed. Thanks in advance.
Hey I found another method where I will get the same result of the compass in the map box. I've placed a button and then
func mapViewRegionIsChanging(_ mapView: MGLMapView) {
compassViewUpdate(direction: Double(bearing))
inside compassViewUpdate method
func compassViewUpdate(direction:Double) {
self.compassButton.transform = CGAffineTransform(rotationAngle: CGFloat(-direction.degreesToRadians))
}
#Sarang here is a work around. Happy coding.
A built-in approach that's available in the Mapbox iOS SDK. Using these methods
to move the compass will avoid the distortion caused by .frame and .center
changes.
Generally Changing the CompassView Location
mapView.compassViewPosition = .bottomLeft
mapView.compassViewPosition = .bottomRight
mapView.compassViewPosition = .topLeft
mapView.compassViewPosition = .topRight
Use compassViewPosition to get the compass generally where you want it on the
screen.
Fine Tuning the CompassView Location
mapView.compassViewMargins = CGPoint(x: 64, y: 16)
Default x, y = 8, 8
The margin is to the outside/along the border of the view. So for .bottomLeft
the compass view will move 56 right and 8 up versus .topRight where the
compass will move 56 left and 8 down.
try changing the compassView.center instead of changing its origin or frame. I am guessing it has approximately 40 pixels width x height, and I changed to bottom left using the following:
let centerCalc = CGRect(x: 5, y: frame.height - 45, width: 40, height: 40)
compassView.center = CGPoint(x: centerCalc.midX, y: centerCalc.midY)
It should work!
Related
I have a SpriteKit platformer that uses a tile map for a background. The background in question is positioned 1 screen-height above the main content (it's positioned off-screen), acting as a forest canopy above the player. I accomplish that programmatically, like this:
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
let columns = 20
let rows = 1
let tileSize = CGSize(width: screenWidth, height: screenHeight)
let container = SKSpriteNode()
let tileDefinition = SKTileDefinition(texture: MainData.textureAtlas.textureNamed("someTexture"), size: CGSize(width: screenWidth, height: screenHeight))
let tileGroup = SKTileGroup(tileDefinition: tileDefinition)
let tileSet = SKTileSet(tileGroups: [tileGroup])
let layer = SKTileMapNode(tileSet: tileSet, columns: columns, rows: rows, tileSize: tileSize)
container.position = CGPoint(x: screenWidth*0.5, y: screenHeight*1.5)
container.size = CGSize(width: CGFloat(columns)*screenWidth, height: screenHeight)
container.zPosition = 3.0
layer.fill(with: tileGroup)
container.addChild(layer)
addChild(container)
A camera node follows the player.
The problem: If the player jumps up, the SKTileMapNode disappears when he comes back down. It never reappears. Its parent node, container, remains visible, so I think the problem is with the SKTileMapNode, not the container.
What I've tried:
I've tried the following, with numbers 2-5 being checked for the SKTileMapNode:
Setting view.shouldCullNonVisibleNodes = false.
Checking the alpha value. It's always 1.0.
Checking the position. It's always CGPoint(x: 0, y: 0).
Checking the anchorPoint. It's always CGPoint(x: 0.5, y: 0.5).
Checking the zPosition. It does not change, and there are no other nodes that could be obscuring the SKTileMapNode or its parent. Setting a higher value has no effect on the problem.
Checking that container remains visible. It does.
On culling:
It seems like the problem should be related to culling, but setting view.shouldCullNonVisibleNodes=false has no effect on the situation. I also checked to make sure the SKTileMapNode is always present as a child node of container. It is. I suppose this means that the node is not being culled. However, if I position container so that it's always on-screen, the problem does not occur at all; the SKTileMapNode remains visible. This leaves me very confused because it seems like these are conflicting facts.
On devices:
Using the simulator, at least, the problem does not occur on the older-style iPhones such as the SE and the iPhone 8. It
only happens on the newer iPhones, such as the iPhone 11 and iPhone
12. Having access to an iPhone 11, I can confirm that the problem is occurring on real devices, too.
Question: Why is my SKTileMapNode disappearing when off-camera (even with culling disabled)? How can I keep this node visible?
Thank you!
It seems that I've solved the problem.
I'm presenting my scene via SwiftUI SpriteView, which I had configured to allow background transparency, like this:
SpriteView(scene: theScene, options: [.allowsTransparency])
Removing the transparency option solved the problem:
SpriteView(scene: theScene)
Now, why should this be the case? I have no idea.
Edit
This is what I want visualised (ignore the ugly red line, it just indicates the movement of the UIView):
I want to have a UIView that is initialised in the middle of the screen. After that, I want to give it a push upwards and the gravity pulls it down till it is off the screen. My old question works with a UIPushBehaviour, UIDynamicBehaviour and a UIGravityBehaviour (see below). Matt pointed out a UIPushBehaviour is maybe not the right choice, since it not work out well across every screen size available on iOS.
I can do this with a UIView.animate function, but it is really static and does not look natural. With the UIPushBehaviour, UIDynamicBehaviour and UIGravityBehaviour, it looks really nice but the UIPushBehaviour's magnitude can not be calculated across every screen size to give the same ending point of the UIView's x and y position.
Question
How can I initialise a UIView in the middle of the screen, 'pull up' that UIView (with some change in the x position) and let the gravity (or something else) pulls it down until it is off the screen? It is important that the change in the x and y position will be the same on every screen size.
Below is my old question
I have a UIPushBehaviour with instantaneous as mode in which I push some UIViews around. The greater the screen size, the less it pushes.
I also have a UIDynamicItemBehavior with resistance set to 1, I think this is one the main reasons it is different in each screen size (correct me if I am wrong).
I want a function that will push the UIView to the same ending point, with the same speed, duration and ending point regardless of the screen size.
I tried to make a relative magnitude without any luck:
For the iPhone 5S, let's say a magnitude of 0.5 would touch a UIView from the middle to the top. I wanted to calculate the magnitude across all devices like this:
let y = 0.5 / 520 // 5S screen height
magnitude = self.view.frame.height * y
For the iPhone 8, it has a very different output and is not working. When reading the docs, I thought I would understand it. I thought 1 magnitude represents 100 pixels, but it is clearly not that case.
Is there any way I can calculate a magnitude to, for example, move a UIView from the middle to the right?
I made a project here. There is a black UIView that get's pushed to the edges on an iPhone 5, but not on the iPhone 8.
Solution
You need to scale the push amount relative to the size of the screen so your view always ends in the same place. To do this, adjusting the UIPushBehavior's pushDirection vector works quite well. In this case, I set the push direction to be proportional to the bounds of the view, and scaled it down by a constant factor.
let push = UIPushBehavior(items: [pushView], mode: .instantaneous)
let pushFactor: CGFloat = 0.01
push.pushDirection = CGVector(dx: -view.bounds.width * pushFactor, dy: -view.bounds.height * pushFactor)
animator.addBehavior(push)
You may need to adjust some constants to get the exact animation you want. The constants you can adjust are:
Gravity magnitude (currently 0.3)
Push factor (currently 0.01)
Depending on your needs, you may need to scale the gravity magnitude proportional to the size of the screen as well.
Note: These constants will need to change based on the size of your animated view, since UIKit Dynamics treats the size of the view as its mass. If your view needs to be dynamically sized, you will need to scale your constants according to the size of the animated view.
Edit regarding comments on the original question:
Views of varying sizes: Like I mentioned in my note above, you'll need to apply an additional factor to account for the "mass" of the views. Something like view.frame.height * view.frame.width * someConstant should work well.
iPad screen size: Currently the pushFactor is applied to both the dx and dy components of the vector. Because iPads have a different aspect ratio, you'll need to split this into two constants, maybe xPushFactor and yPushFactor, which can account for the differences in aspect ratio.
Examples
iPhone 8
iPhone SE
Full Playground Source Code
Copy and paste this code into a Swift playground to see it in action. I've included the sizes of various iPhone screens, so just uncomment the size you want to easily test the animation on different device sizes. Most of the interesting/relevant code is in viewDidAppear.
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
let pushView = UIView()
var animator: UIDynamicAnimator!
override func viewDidLoad() {
super.viewDidLoad()
view.frame = CGRect(x: 0, y: 0, width: 568, height: 320) // iPhone SE
// view.frame = CGRect(x: 0, y: 0, width: 667, height: 375) // iPhone 8
// view.frame = CGRect(x: 0, y: 0, width: 736, height: 414) // iPhone 8+
// view.frame = CGRect(x: 0, y: 0, width: 812, height: 375) // iPhone X
view.backgroundColor = .white
let pushViewSize = CGSize(width: 200, height: 150)
pushView.frame = CGRect(x: view.bounds.midX - pushViewSize.width / 2, y: view.bounds.midY - pushViewSize.height / 2, width: pushViewSize.width, height: pushViewSize.height)
pushView.backgroundColor = .red
view.addSubview(pushView)
animator = UIDynamicAnimator(referenceView: self.view)
let dynamic = UIDynamicItemBehavior()
dynamic.resistance = 1
animator.addBehavior(dynamic)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let gravity = UIGravityBehavior(items: [pushView])
gravity.magnitude = 0.3
animator.addBehavior(gravity)
let push = UIPushBehavior(items: [pushView], mode: .instantaneous)
let pushFactor: CGFloat = 0.01
push.pushDirection = CGVector(dx: -view.bounds.width * pushFactor, dy: -view.bounds.height * pushFactor)
animator.addBehavior(push)
}
}
let vc = ViewController()
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = vc.view
I need to create a component that will let users choose from 2 choices in images. At first, you see 2 images side by side with a "handle" in the middle. If you move the handle to the left, you will see more of the image to right and less of the left image, as to reveal the right image, and vice versa.
Technically, I have 2 full size UIImageViews one on top of the other, and they are masked. I have a pan gesture and when the user slides the handle, the handle moves and the masks update themselves to adjust to "the new middle".
Here's the code responsible for adjusting the image mask. The constant is calculated in the method called by the gesture. I know my calculations of that constant are good because the "handle" and the masks are updated correctly.
BUT
the masks gets updated too late and when dragging, we see it being adjusted too late.
func adjustImagesMasks(to constant: CGFloat) {
choiceImageA.mask?.willChangeValue(forKey: "frame")
choiceImageB.mask?.willChangeValue(forKey: "frame")
let separationPoint: CGFloat = self.frame.width / 2.0 + constant
maskA.backgroundColor = UIColor.black.cgColor
maskA.frame = CGRect(origin: .zero, size: CGSize(width: separationPoint, height: self.frame.size.height))
maskB.backgroundColor = UIColor.black.cgColor
maskB.frame = CGRect(x: separationPoint, y: 0, width: self.frame.width - separationPoint, height: self.frame.size.height)
choiceImageA.mask?.didChangeValue(forKey: "frame")
choiceImageB.mask?.didChangeValue(forKey: "frame")
maskA.drawsAsynchronously = true
maskB.drawsAsynchronously = true
self.setNeedsDisplay()
maskA.setNeedsDisplay()
maskA.displayIfNeeded()
maskB.setNeedsDisplay()
maskB.displayIfNeeded()
}
The image views have their masks setup like this:
maskA = CALayer()
maskB = CALayer()
choiceImageA.layer.mask = maskA
choiceImageA.layer.masksToBounds = true
choiceImageB.layer.mask = maskB
choiceImageB.layer.masksToBounds = true
So to recap, my question is really about performance. The image views are being correctly adjusted, but too slowly. The "handle", which is positioned with constraints, get updated really quickly.
So apparently, CALayer tries to animate most of the changes to its properties. So the delay I was seeing was in fact due to an animation.
I resolved my issue by surrounding the call to adjustImagesMasks() with CATransaction.setValue(kCFBooleanTrue, forKey:kCATransactionDisableActions) and CATransaction.commit(). So for this transaction, I'm asking to not animate the changes. Because this is continuous (with the panning gesture), it is seemless.
Full code here:
CATransaction.setValue(kCFBooleanTrue, forKey:kCATransactionDisableActions)
adjustImagesMasks(to: newConstant)
CATransaction.commit()```.
This other post helped me a lot. There's a nice explanation too.
Hope this helps someone else.
Very new to Sprite Kit and i'm doing some reading now, but wanted to ask about something that I haven't found the best answer to yet.
I'm doing a tutorial with some code that creates a background, and then adds a label to show a score. I started changing the label code to position it on the top-left corner of the screen.
Here is the code (with my edits to the label, gameLabel):
let screenSize = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
let background = SKSpriteNode(imageNamed: "background")
background.position = CGPoint(x: screenWidth / 10, y: (screenHeight / 15) - 100)
background.blendMode = .Replace
background.zPosition = -1
addChild(background)
gameScore = SKLabelNode(fontNamed: "Chalkduster")
gameScore.text = "Score: 0"
gameScore.position = CGPoint(x: screenWidth / 10, y: (screenHeight / 15) - 100)
gameScore.horizontalAlignmentMode = .Left
gameScore.verticalAlignmentMode = .Top
gameScore.fontSize = 48
addChild(gameScore)
So the label is not displaying in any case, and my assumption is this:
Because i'm adding the background first, the label is getting added within the confines of the background, therefore it needs to be positioned differently, perhaps using the label size instead of the screen size?
My questions:
How can I get the label to always appear in the top left?
The author chose a hard-coded CGPoint for this background image and then said it has to be on an iPad, but i'd like to do it for iPhone 6/plus in landscape as well as iPad. Is there a way I can just make it work on both devices without having to specify a CGPoint like that? Can't it just scale and work regardless?
Thanks and apologies if these are basic questions - i'm going to do my best to continue reading on the subject..
Your question has a simple answer to it. You have forgotten and missed out somethings in your code, and I can show you these things.
In your code you set the background ZPosition to -1, the smallest number will always appear at the back. So if you set the SKLabelNode to a bigger zPosition it will always appear at the front, as maybe there may be a problem with rendering, as I have also experience like these, I fix it this way:
Before you add the LabelNode set it's property to this:
gamescore.zPosition = 0
0, In this case could just be anything bigger than the backgrounds(or the node that you want to appear at the back). So this just tells the compiler that you want the LabelNode to appear at the front, or in front of the Background.
If you want to make a universal app or game, with SpriteKit you will need to add some extra code to your game or app. Since I think that it is better to give you a good tutorial to show you instead of showing you some basics, I will give you a good link on how to do this:
SpriteKit: how to make a universal app
I hope this helps, and don't worry this took me some time to figure out my self.
When I create a SpriteKit game from scratch using Swift, in the didMoveToView method of my GameScene I write:
print(frame.size.width)
print(frame.size.height)
I get back.
1024.0
768.0
My app is currently running is portrait mode. I thought the frame was the smallest rectangle that could contain the node.
If I add a shape to the screen to confirm what I am reading I add the following code:
anchorPoint = CGPoint(x: 0.5, y: 0.5)
let leftShape = SKShapeNode(rectOfSize: CGSize(width: frame.size.width/8, height: frame.size.height/2))
leftShape.fillColor = UIColor.redColor().colorWithAlphaComponent(0.2)
leftShape.strokeColor = UIColor.clearColor()
leftShape.name = "left"
addChild(leftShape)
I get the following:
None of this makes sense to me. The math doesn't add up. It seems like I missing a fundamental part the sizing in Spritekit or iOS in general.
I am using xcode 7 beta 3 which is Swift 2.0. Although I am using Swift to code this I am not opposed to a suggestion written in Objective-C
I had a similar problem and i solved putting this into gameViewController
scene.size = skView.bounds.size