view and scene not correct on iOS device rotation - ios

When doing a rotation, I'm getting an out of place view/scene. The viewWillTransition method is as below:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
{
let newRect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
let newSize = CGSize(width: size.width, height: size.height)
self.view.frame = newRect
let skView = self.view as! SKView
if let sceneNode = skView.scene as! GameScene? {
sceneNode.size = newSize
sceneNode.position = CGPoint(x: self.view.frame.midX, y: self.view.frame.midY)
}
}
Can someone please let me know what I'm doing wrong?
Thank you

Try this,
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let skView = self.view as! SKView
if let sceneNode = skView.scene as! GameScene? {
sceneNode.size = self.view.frame.size
sceneNode.position = CGPoint(x: self.view.frame.midX, y: self.view.frame.midY)
}
}

Thx #AshokPolu!
I prefer this because the size will be animated along device rotate animation.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animateAlongsideTransition(in: self.view, animation: { (_) in
if let skView = self.view as? SKView, let scene = skView.scene as? GameScene {
scene.size = self.view.frame.size
scene.position = CGPoint(x: self.view.frame.midX, y: self.view.frame.midY)
}
}, completion: nil)
}

Related

Display UIView behind SKScene in GameViewController

I have a basic SpriteKit game.
What I basically want to do is function create_new_ui() to create a UIView and to hide it behind the scene.
override func viewDidLoad()
{
super.viewDidLoad()
create_new_ui()
if let view = self.view as! SKView?
{
// Load the SKScene from 'GameScene.sks'
let scene = MainScene()
// Set the scale mode to scale to fit the window
scene.scaleMode = .resizeFill
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
// Present the scene
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
func create_new_ui()
{
let ui_view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
ui_view.center = CGPoint(x: self.view.frame.size.width / 2, y: self.view.frame.size.height / 2)
ui_view.backgroundColor = UIColor.red
self.view.addSubview(ui_view)
}
Can you tell me how can I move the ui_view behind the SKScene?
I tried sendSubviewToBack(_:) but had no effect. Any ideas?
There are several methods to do it. One way is directly adding it to the window after view did appear.:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addFakeView()
}
func addFakeView(){
let fview = UIView.init(frame: CGRect.init(x: 100, y: 400, width: 100, height: 100))
fview.backgroundColor = UIColor.green
view.window?.insertSubview(fview, at: 0)
}

UIView autorotation issue

I am getting issues in autorotation of a UIView which is nothing but a red line of width 4 and height half of superview's height in landscape mode, and in portrait mode the height is 4 points and width is half of superview width. You can see the issue in the gif. As I rotate from landscape to portrait, the effect of autorotation is not smooth and with distortions.
Here is the code. What am I doing wrong?
private var myView:UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
myView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width/2, height: 4))
myView.backgroundColor = UIColor.red
view.addSubview(myView)
myView.center = CGPoint(x: view.bounds.width/2, y: view.bounds.height/2)
}
private func layoutMyView() {
let orientation = UIApplication.shared.statusBarOrientation
if orientation == .landscapeLeft || orientation == .landscapeRight {
myView.frame = CGRect(x: 0, y: 0, width: view.bounds.width/2, height: 4)
} else {
myView.frame = CGRect(x: 0, y: 0, width: 4, height: view.bounds.height/2)
}
myView.center = CGPoint(x: view.bounds.width/2, y: view.bounds.height/2)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let orientation = UIApplication.shared.statusBarOrientation
NSLog("View will transition to \(size), \(orientation.rawValue)")
if size.width < size.height {
NSLog("Portrait mode")
} else {
NSLog("Landscape mode")
}
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { [unowned self] (_) in
let orient = UIApplication.shared.statusBarOrientation
self.layoutMyView()
}, completion: { [unowned self] (UIViewControllerTransitionCoordinatorContext) -> Void in
let orient = UIApplication.shared.statusBarOrientation
self.layoutMyView()
NSLog("rotation completed to \(orient.rawValue), \(self.myView.frame)")
})
}
There are a few potential options:
In animate(alongsideTransition:completion:), do a keyframe animation to set mid animation point so you don’t end up with that curious boxy feel mid animation. E.g. this shrink it down to a 4x4 box
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let origin = CGPoint(x: (view.bounds.midX + view.bounds.midY) / 2 - 2,
y: (view.bounds.midX + view.bounds.midY) / 2 - 2)
coordinator.animate(alongsideTransition: { _ in
UIView.animateKeyframes(withDuration: coordinator.transitionDuration, delay: 0, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
self.myView.frame = CGRect(origin: origin, size: CGSize(width: 4, height: 4))
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
self.layoutMyView()
}
})
})
}
There are many possible variations on this theme. This still animates the view, but gives it a more “deliberate” sort of vibe and doesn’t lose the “line” feel of it.
Rather than rendering a “line” with UIView, use CAShapeLayer and update its path.
You could run a CADisplayLink while transition is in progress, and then update the frame to whatever you want as the animation progresses.
Instead of animating the frame, you could animate the transform of this view, possibly rotating it in the opposite direction that the view is rotating.

How can I present a UIViewController on top of another UIViewController, but not taking up the full screen

I am writing a custom UIStoryboardSegue and I intend to present a the Destination ViewController over the Source ViewController, with animation. Here is the code for the animation
override func perform() {
let topVCView = self.destination.view
let bottomVCView = self.source.view
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
topVCView!.frame = CGRect(x: 0.0 - screenWidth, y: 0, width: 150.0, height: screenHeight)
let window = UIApplication.shared.keyWindow
window?.insertSubview(topVCView!, aboveSubview: bottomVCView!)
window?.makeKeyAndVisible()
UIView.animate(withDuration: 0.2, animations: {
topVCView!.frame = topVCView!.frame.offsetBy(dx: screenWidth, dy: 0.0)
}, completion: ({(finished) -> Void in
}))
}
The animation works gracefully enough. But I cannot get callback on UIButton actions in my destinationVC. How can I get that to work?
Edit: The red VC is my source VC and the green one is my destination VC
override func perform() {
let contentVC = self.sourceViewController;
let sideBarVC = self.destinationViewController;
var sideFrame = sideBarVC.view.frame
sideBarVC.view.frame = CGRect(x: -(contentVC.view.frame.size.width), y: 0, width: contentVC.view.frame.size.width/2, height: contentVC.view.frame.size.height)
sideFrame = sideFrame.offsetBy(dx: -1 * contentVC.view.frame.size.width, dy: 0)
contentVC.view.superview?.addSubview(sideBarVC.view)
UIView.animate(withDuration: 0.3) {
sideBarVC.view.frame = sideFrame
contentVC.view.alpha = 0.5
}
}

Check Landscape/Portrait orientation in an iMessage app (extension)

Have seen a lot of solutions to check the orientation, but strangely, none works!!
Below is the code snippet,
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let screenSize = UIScreen.main.bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
print("Screen Width = \(screenWidth)")
print("Screen Height = \(screenHeight)")
if (screenWidth > screenHeight) {
print("Landscape")
alertView.removeFromSuperview()
messageView.addGestureRecognizer(tapTextView)
}
else {
print("Portrait")
setupLandscapeAlertView()
}
}
The other method which is used to setup the view is,
fileprivate func setupLandscapeAlertView() {
messageView.removeGestureRecognizer(tapTextView)
alertView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height))
alertView.backgroundColor = UIColor.clear
alertView.isUserInteractionEnabled = false
let transparentView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.height, height: self.view.frame.width))
transparentView.backgroundColor = UIColor.gray
transparentView.backgroundColor = transparentView.backgroundColor!.withAlphaComponent(0.6)
transparentView.isUserInteractionEnabled = false
alertView.addSubview(transparentView)
let blurEffect = UIBlurEffect(style: .regular)
let blurredEffectView = UIVisualEffectView(effect: blurEffect)
blurredEffectView.frame = transparentView.bounds
blurredEffectView.isUserInteractionEnabled = false
alertView.addSubview(blurredEffectView)
let imageView = UIImageView(image: UIImage(named: "LandscapeAlert"))
imageView.frame = CGRect(x: 70, y: 75, width: imageView.frame.width, height: imageView.frame.height)
imageView.contentMode = UIViewContentMode.scaleAspectFit
imageView.contentMode = UIViewContentMode.center
imageView.isUserInteractionEnabled = false
alertView.addSubview(imageView)
self.view.addSubview(alertView)
}
Another thing is that, this image isn't centered. How do I go about that? Again, too many solutions, but nothing does the job.
Not sure if I'm doing anything wrong. :-/
This may be because viewWillTransition executes before the view actually flips. If you do a simple print("height width \(view.frame.height) \(view.frame.width)") inside the viewWillTransition block you will see.

The right way to do a Twitter-like mask loading animation?

I've been trying to make a loading screen similar to Twitter's loading overlay here:
I'm not sure if the way I've approached this is the "right" way to do things at all. My concerns are many:
Is there a better way to pick the mask image I'm using for various screen sizes? The mask needs to extend to the edges of the screen to work with the background color, so I'm making it the size of the phone's possible bounds.
Are animations being handled correctly? This is my first use of CATransaction for anything.
Should I be adding the view to the tab bar controller's view like I'm doing?
Is there any way to abstract this out, or a better place to put this in the app's lifecycle? As of now I need to duplicate this code and place it any controller that could be a starting point (two controllers as of now).
You can see what I've written so far below:
override func viewDidLoad() {
super.viewDidLoad()
prepareLoadingOverlay()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
animateMask()
}
var mask: CALayer?
var blueView: UIImageView?
func prepareLoadingOverlay() {
let size = UIScreen.mainScreen().bounds.size
var cutoutImage: UIImage?
switch size.height {
case 420:
cutoutImage = CutoutMap.Image480 // UIImage
case 568:
cutoutImage = CutoutMap.Image568
case 667:
cutoutImage = CutoutMap.Image667
case 736:
cutoutImage = CutoutMap.Image736
default:
cutoutImage = CutoutMap.Image667
}
if let tabBarViewController = self.tabBarController,
let cutout = cutoutImage {
let containerFrame = tabBarViewController.view.frame
blueView = UIImageView.init(frame: containerFrame)
blueView!.image = UIImage.imageFromColor(Color.Blue)
self.mask = CALayer()
self.mask?.contents = cutout.CGImage
self.mask?.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
self.mask?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.mask?.position = CGPoint(x: containerFrame.size.width/2, y: containerFrame.size.height/2)
tabBarViewController.view.addSubview(blueView!)
blueView!.layer.mask = self.mask
}
}
func animateMask() {
let size = UIScreen.mainScreen().bounds.size
if let mask = self.mask {
CATransaction.begin()
CATransaction.setAnimationDuration(1)
CATransaction.setCompletionBlock { () -> Void in
CATransaction.begin()
CATransaction.setAnimationDuration(1)
CATransaction.setCompletionBlock { () -> Void in
self.blueView?.removeFromSuperview()
}
mask.bounds = CGRect(x: 0, y: 0, width: size.width*15, height: size.height*15)
CATransaction.commit()
}
mask.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
CATransaction.commit()
}
}
Any help on this would be greatly appreciated!

Resources