Ios: How to Run a Function After Device Has Rotated (Swift)

iOS: How to run a function after Device has Rotated (Swift)

The viewWillTransitionToSize delegate method gets called with a UIViewControllerTransitionCoordinator conforming object. A method that protocol declares is animateAlongsideTransition(_:animation, completion:). You can use that to have code execute after the transition is complete.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: nil) { _ in
// Your code here
}
}

Swift - How to detect orientation changes

let const = "Background" //image name
let const2 = "GreyBackground" // image name
@IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()

imageView.image = UIImage(named: const)
// Do any additional setup after loading the view.
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isLandscape {
print("Landscape")
imageView.image = UIImage(named: const2)
} else {
print("Portrait")
imageView.image = UIImage(named: const)
}
}

in swift 4.1, how do you listen for when a view controller has finished rotating?

In the viewWillTransition you have a completion block to show when the rotation has finished, to check when the animation has finished it has to be in the animation completion block.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)

And inside you have to call the super.
And the coordinator.animate where there is the completion block

super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (_) in

// Code while it is transitioning

}, completion: _ in
// Completion block
)

How to update the UI when the device orientation changed in Swift?

Actually by using

loginView.frame = CGRect(origin: .zero, size: view.bounds.size)

you assign a frame to your login view which will not change on device rotation.

you might wanna use a constraint approach here as well:

// try replacing
// loginView.frame = CGRect(origin: .zero, size: view.bounds.size)

// with
loginView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
loginView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
loginView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
loginView.topAnchor.constraint(equalTo: view.topAnchor),
loginView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])

Another approach would be updating the frame of your login view e.g.

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
loginView.frame = CGRect(origin: .zero, size: view.bounds.size)
}

Last but not least you can use autoresizingMask in viewDidLoad

loginView.frame = CGRect(origin: .zero, size: view.bounds.size)
loginView.autoresizingMask = [.flexibleHeight, .flexibleWidth]

P.S. My previous comment was misleading since the login frame wasn't set in the setupStackView() function

and my final comment:

// you can replace
loginView.frame = CGRect(origin: .zero, size: view.bounds.size)
// with
loginView.frame = view.bounds

what method will called when we start to rotate device and after it finished

From Apple Docs:

Sent to the view controller just before the user interface begins rotating.

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

Sent to the view controller after the user interface rotates:

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation

See more here: UIViewController Class Reference -> Responding to View Rotation Events

ATTENTION:
This is deprecated, see this post

Keep view portrait but let iOS Rotate with phone

One of possible solutions is to split your application to "rotating" part and "non-rotating" part using windows.

It is not an ideal choice but lately the tools that we get do not give us much options. The problem you are facing when using this procedure is that you can have some chaos when presenting new view controllers. In your case this may not be issue at all but still...

In short what you do is:

  1. Leave main window as it is but enable your application to rotate into all directions that you need
  2. Create a view controller that only supports one orientation (whichever you prefer) and show it a new window over your main one but below status bar (Default behavior)
  3. Create a view controller with transparent background and that can pass touch events through to the window below it. Also show it in a new window over the previous one. Also this window needs to pass touch events through to bottom window.

You can create all of these in code or with storyboard. But there are a few components to manage. These are all I used when validating this approach:

class StandStillViewController: WindowViewController {

override func viewDidLoad() {
super.viewDidLoad()

// Do any additional setup after loading the view.
}

func setupManually() {
self.view = DelegatedTouchEventsView()
self.view.backgroundColor = UIColor.darkGray

let label = UILabel(frame: .zero)
label.text = "A part of this app that stands still"
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
view.addConstraint(.init(item: label, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0.0))
view.addConstraint(.init(item: label, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0.0))

let button = UIButton(frame: .zero)
button.setTitle("Test button", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(testButton), for: .touchUpInside)
view.addSubview(button)
view.addConstraint(.init(item: button, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0.0))
view.addConstraint(.init(item: button, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 50.0))
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .portrait }
override var shouldAutorotate: Bool { false }
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { return .portrait }

@IBAction private func testButton() {
print("Button was pressed")
}


}

This is the controller at the bottom. I expect this one will host your game. You need to preserve

override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .portrait }
override var shouldAutorotate: Bool { false }
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { return .portrait }

the rest may be changed, removed.



class RotatingViewController: WindowViewController {

override func viewDidLoad() {
super.viewDidLoad()
}

func setupManually() {
self.view = DelegatedTouchEventsView()
self.view.backgroundColor = UIColor.clear // Want to see through it

let label = UILabel(frame: .zero)
label.text = "A Rotating part of this app"
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
view.addConstraint(.init(item: label, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0.0))
view.addConstraint(.init(item: label, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 60.0))

let button = UIButton(frame: .zero)
button.setTitle("Test rotating button", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(testButton), for: .touchUpInside)
view.addSubview(button)
view.addConstraint(.init(item: button, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0.0))
view.addConstraint(.init(item: button, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: -50.0))
}

@IBAction private func testButton() {
print("Rotating button was pressed")
}

}

This controller will deal with rotating stuff. You need to preserve

self.view = DelegatedTouchEventsView()
self.view.backgroundColor = UIColor.clear // Want to see through it

which may both be set in storyboard as well. Anything you put onto this controller will rotate with your device. A nice place to put some GUI stuff for instance.



class WindowViewController: UIViewController {

private var window: UIWindow?

func shownInNewWindow(delegatesTouchEvents: Bool, baseWindow: UIWindow? = nil) {
let scene = baseWindow?.windowScene ?? UIApplication.shared.windows.first!.windowScene!
let newWindow: UIWindow
if delegatesTouchEvents {
newWindow = DelegatedTouchEventsWindow(windowScene: scene)
} else {
newWindow = UIWindow(windowScene: scene)
}


newWindow.rootViewController = self
newWindow.windowLevel = .normal
newWindow.makeKeyAndVisible()

self.window = newWindow
}

func dismissFromWindow(completion: (() -> Void)? = nil) {
removeFromWindow()
completion?()
}

func removeFromWindow() {
self.window?.isHidden = true
self.window = nil
}

}

This is what I used as base class for both view controllers above. It is not much but it allows view controllers to be shown in a new window. This code was pasted from one of my older projects and could use some minor improvements. But it does works so...



class DelegatedTouchEventsView: UIView {

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
return view == self ? nil : view
}

}

class DelegatedTouchEventsWindow: UIWindow {

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
return view == self ? nil : view
}

}

These are the two subclasses to let touch events go through. A quick explanation on how this works: When event is received your system will first send this event through your view hierarchy asking "who is going to handle this event?". Returning nil means "not me" and default for UIView is self. So in this code we say: "If any of my subviews wants to handle this event (such as a button) then it may handle this event. But if none of them wants to handle them then neither I will."
And UIWindow is a subclass of UIView so we need to deal with both of them.



class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

// let standingStillController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "StandStillViewController") as! StandStillViewController
// standingStillController.shownInNewWindow(delegatesTouchEvents: false, baseWindow: self.view.window)
//
// let rotatingViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "RotatingViewController") as! RotatingViewController
// rotatingViewController.shownInNewWindow(delegatesTouchEvents: true, baseWindow: self.view.window)

let standingStillController = StandStillViewController()
standingStillController.setupManually()
standingStillController.shownInNewWindow(delegatesTouchEvents: false, baseWindow: self.view.window)

let rotatingViewController = RotatingViewController()
rotatingViewController.setupManually()
rotatingViewController.shownInNewWindow(delegatesTouchEvents: true, baseWindow: self.view.window)
}


}

This is an example on how to use it all together. As promised, either using Storyboards or manually, both should work.

Seems like a lot of work but you need to set it up once and never look at it again.

How to run a class again after is has been initialised

The documentation isn't great for this library, but here's a solution for you. I started with the example project on gitHub, but I removed the init function and made the data var

class CustomMacawView: MacawView {

static var data: [Double] = [101, 142, 66, 178, 92]
static let palette = [0xf08c00, 0xbf1a04, 0xffd505, 0x8fcc16, 0xd1aae3].map { val in Color(val: val)}

public func updateData(newData : [Double])
{
CustomMacawView.data = newData
updateDisplay()
}

public func updateDisplay()
{
let chart = CustomMacawView.createChart()
self.node = Group(contents: [chart])
}

in my main view controller, I have a button to generate new data

@IBAction func cmdUpdateGraph(_ sender: Any) {
macawView.updateData(newData: [Double.random(in: 1...100),
Double.random(in: 1...100),
Double.random(in: 1...100),
Double.random(in: 1...100),
Double.random(in: 1...100)]) }

Force device rotate issue on iOS

Step 1:- Please define a below-mentioned variable in app delegate.

var shouldRotate : Bool = false

Step 2:- Implement the following delegate method as below.

//MARK:- Set Device Orientation Delegate Method

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return shouldRotate ? .landscapeRight : .portrait
}

Step 3 :- Define constant class and add "Set Portrait Orientation" and "Set Landscape Right Orientation" function in the class.

//MARK:- Set Portrait Orientation
func setPortraitOrientation() {
appDelegateInstance.shouldRotate = false
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
}

//MARK:- Set Landscape Right Orientation

func setLandscapeRightOrientation() {
appDelegateInstance.shouldRotate = true
UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
}

Step 4:- Use the following code in your class (Target landscape class)

Step 4.1:- In view will appear call set Landscape Right Orientation function shown below.

 override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
setLandscapeRightOrientation()
}

Step 4.2 :- When you leave the screen implement the following code in the action.

//MARK:- Left Bar Button Tapped Delegate Method

func leftBarButtonTapped() {
_ = self.navigationController?.popViewController(animated: false)
setPortraitOrientation()
}

Happy Coding! Cheers!!

Swift 4: UI fails to update after device rotation

Note that the constraint method creates new for your image view.

When you first call setupLayout in viewDidLoad, width and height constraints of 200 are added to the image view. Then you rotate the device to change to landscape. setupLayout is called again. This time it adds width and height constraints of 100, but it does not deactivate the constraints with constant 200 that you previously added. Doing this line:

imageView.widthAnchor.constraint(equalToConstant: 200).isActive = false

Creates a new inactive constraint, not deactivates an old one.

What you should do is to store the width and height constraints as properties of ViewController:

var widthConstraint: NSLayoutConstraint!
var heightConstraint: NSLayoutConstraint!

And in setupLayout, assign to those properties:

private func setupLayout(imageView: UIImageView){
widthConstraint = imageView.widthAnchor.constraint(equalToConstant: 200)
heightConstraint = imageView.heightAnchor.constraint(equalToConstant: 200)
widthConstraint.isActive = true
heightConstraint.isActive = true
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true

}

And you should only call setupLayout once.

Then you create another method called updateConstraints that update the constants of widthConstraint and heightConstraint:

private func updateConstraints() {
if UIDevice.current.orientation.isLandscape {
heightConstraint.constant = 100
widthConstraint.constant = 100
} else {
heightConstraint.constant = 200
widthConstraint.constant = 200
}
}

Call this in viewWillTransitionToSize instead of setupLayout.



Related Topics



Leave a reply



Submit