I am trying to create a view that doesn't rotate with device orientation. I want just this one view to not rotate because I have a UIToolbar that I don't want to rotate. Is this possible? If so, how do I implement it? I am using Swift. Also, is it possible to rotate the UIBarButtonItems with device orientation on the UIToolbar? If so, how do I implement that? Just to restate: I want the view and toolbar within it to not rotate with orientation; I want the buttons on the toolbar to rotate with orientation. Thanks.
Per the View Controller Programing Guide
If you want to temporarily disable automatic rotation, avoid manipulating the orientation masks to do this. Instead, override the shouldAutorotate variable on the initial view controller. This method is called before performing any autorotation. If it returns NO, then the rotation is suppressed.
So you need to subclass 'UINavigationController', implement shouldAutorotate and use your navigation controller class in your storyboard.
Swift 3
override var shouldAutorotate: Bool {
let currentViewController = self.topViewController
return !currentViewController.isKind(of: DetailViewController.classForCoder())
}
Why not just rotate the view in question -90 degrees or +90 degrees as required? I'll happily provide example code if you'd like.
Edit:
I was suggesting something like this (but look at the caveat after the code):
let degrees = 90.0 // or -90, 180 depending on phone's movement.
let rotatedView: UIView = <yourOriginalView>
rotatedView.transform = CGAffineTransform = CGAffineTransform(rotationAngle: degrees * CGFloat(M_PI / 180))
In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame. -- So this might not work for your purposes if you're using autolayout.... And I admit this is a kludge. 😉🙄😉
I see two possibilities, and prefer the second because it doesn't rotate your views twice. I will use the term static view for the single view that you don't want to rotate (= the UIToolbar).
Possibility 1
Subscribe to device orientation changes. Then, when the orientation changes, and the UI rotates, rotate the static view in the opposite direction.
For example when you get a notification that the device is rotated 90° clockwise:
The whole user interface (including the static view) will rotate 90° counterclockwise automatically
Rotate the static view 90° clockwise, to cancel the automatic rotation behaviour.
Possibility 2
Disable the automatic rotation behaviour and then manually rotate all views except the one you don't want to rotate. To rotate multiple views at once: put them in a custom view and rotate that custom view. Here is a step-by-step guide:
Disable automatic rotation in your view controller. Do this by overriding the ViewController's shouldAutoRotate variable.
In interface builder: put everything except the static view in a custom UIView.
Observe device orientation changes and rotate the statusbar and your custom view (you created in step 2) when the device's orientation changes.
An example implementation in swift can be found at: GitHub/Dev1an/Static iOS toolbar
Rotating views
For the question "how to rotate views" have a look at: https://stackoverflow.com/a/28717635/2616297
Straightforward. You should simply override the shouldRotate variable and set it to false for the VC you wish to prevent rotation in.
UPDATED - Swift 3
override var shouldAutorotate: Bool {
return false
}
I used the information from all of the answers here, as well as from this post overriding shouldAutorotate not working in Swift 3, to find the answer to my question. I created a MainNavigationController class within my MainViewController.swift file and then overrided shouldAutorotate like so:
import UIKit
class MainNavigationController: UINavigationController {
override var shouldAutorotate: Bool {
return false }
}
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
The last step was to go into my Main.storyboard file, click on the navigation controller for the view, and change its class to MainNavigationController. Once I did that, it worked perfectly. Thanks to all the people who answered this question.
Related
What I want to to: I want to drag down the whole view of a viewController to dismiss to the parent viewController using a pan gesture recognizer.
The Problem: When I drag the view down, the navigationBar decreases its height and does not look good. When the view returns to its original position, the navigationBar returns to the default size. I want the navigationBar to stay at its size. I also tried to use the new large titles and some other properties of the navigationController/-bar, but that did not solve it.
Note: Everything worked fine already before iOS 11.
My code:
override func viewDidLoad() {
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(dragViewDown(_:)))
navigationController!.view.addGestureRecognizer(panGesture)
}
#IBAction func dragViewDown(_ gesture: UIPanGestureRecognizer) {
if let dragView = gesture.view {
let translation = gesture.translation(in: dragView)
dragView.center.y = (dragView.center.y + translation.y)
gesture.setTranslation(CGPoint.zero, in: dragView)
}
}
This test project only has one viewController and does not provide the dismissal, but the problem is the same as in my working project.
I also uploaded the project to GitHub: https://github.com/maddinK7/navitationBar-pull-down-problem
Does anyone have an idea how to solve this? Thanks in advance.
I want the navigationBar to stay at its size
It is staying at its size. If you check the navigation bar's bounds size height before, during, and after the drag, you will see that it remains the same (probably 44) at all times. What's changing is the drawing extension that causes the drawing of the nav bar to extend up behind the status bar. It can't do that when you pull the whole thing away from the top of the screen, because it is not at the top next to the status bar any more. iOS 11 is more strict about the way it performs this drawing extension, probably because it has to do it in a special way on the iPhone X.
So, let's make sure you're doing this correctly:
Make sure that the navigation bar has a top constraint pinned to the safe area layout guide's top, with a constant of zero.
Make sure that the navigation bar has a delegate that returns .topAttached from position(forBar:).
If you are doing both those things and it doesn't help, you'll have to implement this in some other way entirely. Making the view directly draggable like this, without a custom parent view controller, was always dubious.
When UINavigationController attached top, system will add safe area top margin in the navigation background.
(NOTICE: Background margin will not changed when offset value is between 1 and 0)
So you have to handle attached/detached top event by handle gesture offset to change the right offset and content insets.
You can try the solution in my lib example. ;)
My example include UITableViewController in the UINavigationController, so it will relatively complex.
https://github.com/showang/OverlayModalViewController
I am designing an iOS app in swift, and I am having some difficulty with animations during a controller transition. Specifically, I've implemented a UINavigationControllerDelegate, to listen for when a certain view is pushed. When this view is pushed, I want to hide a bar at the bottom of the screen. My code is working almost perfectly, however whenever I begin an animation on the height of the navigation controller, the current view (which is being removed) animates its height correctly, but the new controller which is being pushed already has the new height from the animation. To put some code to it, the following function is called from my UINavigationControllerDelegate's willShow viewController function:
func animatePlayerVisibility(_ visible: Bool) {
if visible == showingPlayer {
return
}
showingPlayer = visible
let height: CGFloat = visible ? 56.0 : 0.0
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.35) {
self.playerHeight.constant = height
self.viewBottom.constant = height
self.view.layoutIfNeeded()
}
}
'playerHeight' is an IBOutlet to a constraint on the height of the player container view. 'viewBottom' is also an IBOutlet constraint between the bottom of the top container view and the bottom of the screen. Essentially, as long as these two constraints are animated together, it should look nice.
To help visualize the graphical bug, I edited this line
self.viewBottom.constant = height
to
self.viewBottom.constant = height * 2.0
I have created an imgur album of the actual wrong behavior in action:
http://imgur.com/a/znAim
As you can see, the old view controller animates properly, when the new controller already has the new animated size.
Here is the layout of my storyboard:
Any help would be really appreciated. I've been trying to fix this for a while with no success.
EDIT: The view of the animation without the *2 applied.
https://imgur.com/a/2a5Sw
Have you thought about not using UINavigationController? Maybe it will be easier to use ChildViewControllers mechanism. Then with it you can use a powerful autolayouts and have more control over animation (in your case height)
More info about this here
I've created a nice little sample project you can find here!
There are a number of things that could be going wrong, and since I haven't looked over your project personally it's likely I organized things very differently in my sample, but hopefully you will understand it. I think the big thing is that I added a constraint in storyboard to the navigationController's container to the bottom of the root viewController. I don't adjust the height of this container at all when I animate.
I am using a custom camera view overlay in Swift 3. When I move the camera from landscape to portrait, it cuts the camera view size down. Is there a way to check the device orientation and change the frame bounds? Right now the line of code I'm using is
previewLayer?.frame = self.view.bounds
You got several options for this. The main idea is to get a function to be called whenever there is a change in the view.
Option 1, iOS8+
As of iOS 8, all rotation-related methods are deprecated. Instead, rotations are treated as a change in the size of the view controller’s view and are therefore reported using the viewWillTransition(to:with:) method. When the interface orientation changes, UIKit calls this method on the window’s root view controller. That view controller then notifies its child view controllers, propagating the message throughout the view controller hierarchy.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
var previewLayer:CALayer?
if let layer = previewLayer {
layer.frame = self.view.bounds //or:
layer.frame.size = size //If coordinates is (x:0,y:0) you only need to update the size, and that is provided through the function
}
}
Option 2, iOS 6 & 7
In iOS 6 and iOS 7, your app supports the interface orientations defined in your app’s Info.plist file. A view controller can override the supportedInterfaceOrientations method to limit the list of supported orientations. Typically, the system calls this method only on the root view controller of the window or a view controller presented to fill the entire screen; child view controllers use the portion of the window provided for them by their parent view controller and no longer participate directly in decisions about what rotations are supported. The intersection of the app's orientation mask and the view controller's orientation mask is used to determine which orientations a view controller can be rotated into.
When a rotation occurs for a visible view controller, the willRotate(to:duration:), willAnimateRotation(to:duration:), and didRotate(from:) methods are called during the rotation. The viewWillLayoutSubviews() method is also called after the view is resized and positioned by its parent. If a view controller is not visible when an orientation change occurs, then the rotation methods are never called. However, the viewWillLayoutSubviews() method is called when the view becomes visible. Your implementation of this method can call the statusBarOrientation method to determine the device orientation.
When the orientation changes this method will be called and therefor you can update previewLayer.frame there.
override func viewDidLayoutSubviews() {
var previewLayer:CALayer?
if let layer = previewLayer {
layer.frame = self.view.bounds
}
}
or
override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
var previewLayer:CALayer?
if let layer = previewLayer {
layer.frame = self.view.bounds
}
}
I have a view controller with a child view controller.
tab bar controller
|
|
nav controller
|
|
UIPageViewController (should rotate)
|
|
A (Video Player) (shouldn't rotate)
|
|
B (Controls overlay) (should rotate)
A should be forced to stay portrait at all times, but B should be allowed to rotate freely.
I know shouldAutorotate applies to any view controllers and its children, but is there any way to get around this? It seems like I could use shouldAutorotateToInterfaceOrientation, but this is blocked in iOS 8.
I'd like to keep a video player static (so horizontal videos are always horizontal regardless of device orientation), while the controls layer subview overlay is allowed to freely rotate.
I'm using Swift.
I had this exact problem, and found out quickly there's a lot of bad advice floating around about autorotation, especially because iOS 8 handles it differently than previous versions.
First of all, you don't want to apply a counterrotation manually or subscribe to UIDevice orientation changes. Doing a counterrotation will still result in an unsightly animation, and device orientation isn't always the same as interface orientation. Ideally you want the camera preview to stay truly frozen, and your app UI to match the status bar orientation and size as they change, exactly like the native Camera app.
During an orientation change in iOS 8, the window itself rotates rather than the view(s) it contains. You can add the views of multiple view controllers to a single UIWindow, but only the rootViewController will get an opportunity to respond via shouldAutorotate(). Even though you make the rotation decision at the view controller level, it's the parent window that actually rotates, thus rotating all of its subviews (including ones from other view controllers).
The solution is two UIWindow stacked on top of each other, each rotating (or not) with its own root view controller. Most apps only have one, but there's no reason you can't have two and overlay them just like any other UIView subclass.
Here's a working proof-of-concept, which I've also put on GitHub here. Your particular case is a little more complicated because you have a stack of containing view controllers, but the basic idea is the same. I'll touch on some specific points below.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var cameraWindow: UIWindow!
var interfaceWindow: UIWindow!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
let screenBounds = UIScreen.mainScreen().bounds
let inset: CGFloat = fabs(screenBounds.width - screenBounds.height)
cameraWindow = UIWindow(frame: screenBounds)
cameraWindow.rootViewController = CameraViewController()
cameraWindow.backgroundColor = UIColor.blackColor()
cameraWindow.hidden = false
interfaceWindow = UIWindow(frame: CGRectInset(screenBounds, -inset, -inset))
interfaceWindow.rootViewController = InterfaceViewController()
interfaceWindow.backgroundColor = UIColor.clearColor()
interfaceWindow.opaque = false
interfaceWindow.makeKeyAndVisible()
return true
}
}
Setting a negative inset on interfaceWindow makes it slightly larger than the screen bounds, effectively hiding the black rectangular mask you'd see otherwise. Normally you wouldn't notice because the mask rotates with the window, but since the camera window is fixed the mask becomes visible in the corners during rotation.
class CameraViewController: UIViewController {
override func shouldAutorotate() -> Bool {
return false
}
}
Exactly what you'd expect here, just add your own setup for AVCapturePreviewLayer.
class InterfaceViewController: UIViewController {
var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
contentView = UIView(frame: CGRectZero)
contentView.backgroundColor = UIColor.clearColor()
contentView.opaque = false
view.backgroundColor = UIColor.clearColor()
view.opaque = false
view.addSubview(contentView)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let screenBounds = UIScreen.mainScreen().bounds
let offset: CGFloat = fabs(screenBounds.width - screenBounds.height)
view.frame = CGRectOffset(view.bounds, offset, offset)
contentView.frame = view.bounds
}
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.All.rawValue)
}
override func shouldAutorotate() -> Bool {
return true
}
}
The last trick is undoing the negative inset we applied to the window, which we achieve by offsetting view the same amount and treating contentView as the main view.
For your app, interfaceWindow.rootViewController would be your tab bar controller, which in turn contains a navigation controller, etc. All of these views need to be transparent when your camera controller appears so the camera window can show through beneath it. For performance reasons you might consider leaving them opaque and only setting everything to transparent when the camera is actually in use, and set the camera window to hidden when it's not (while also shutting down the capture session).
Sorry to post a novel; I haven't seen this addressed anywhere else and it took me a while to figure out, hopefully it helps you and anyone else who's trying to get the same behavior. Even Apple's AVCam sample app doesn't handle it quite right.
The example repo I posted also includes a version with the camera already set up. Good luck!
You can try this -
Objective -C code if you have its alternative in swift:
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if ()//Place your condition here like if A is visible
{
return UIInterfaceOrientationMaskPortrait;
}
return UIInterfaceOrientationMaskAll;
}
You can subscribe to rotation change notifications, and manually set the rotation transform matrix on the subview you want to rotate.
I'm not sure, but I think you could create an own class for your subview and override the shouldAutorotate method etc. That way it should override the shouldAutorotate from the parent-viewcontroller.
Short answer: No, all visible controllers and views rotate (or don't rotate) together.
Long answer:
First, you must implement autorotate decision functions in the root controller; that may mean making a nav controller subclass.
You can hack your desired behavior by having the parent view autorotate -- but have it manually rotate itself back to appear un-rotated.
Or, you can NOT autorotate, but listen for notifications that the physical device rotated, and manually rotate whatever views you want to, eg: Replicate camera app rotation to landscape IOS 6 iPhone
Also see, fyi:
How to force a UIViewController to Portrait orientation in iOS 6
shouldAutoRotate Method Not Called in iOS6
iOS6: supportedInterfaceOrientations not working (is invoked but the interface still rotates)
How to implement UIViewController rotation in response to orientation changes?
The simplest, most straight-forward answer to this question is to look at Apple's AVCam sample code. The key parts for me were that it:
Uses a view whose layerClass is AVCaptureVideoPreviewLayer.
Sets the videoOrientation of the AVCaptureVideoPreviewLayer's connection to match the application's statusBarOrientation when the view is presented, essentially viewWillAppear(_:).
Sets the videoOrientation to match UIDevice.currentDevice().orientation in viewWillTransitionToSize(_:withTransitionCoordinator:).
Enables autorotation and supports all interface orientations.
I implemented the background window approach described by jstn and it worked fairly well, but the reality is that it is much more complicated than is necessary. AVCam works great and has relatively simple approach.
I want to create a UIVIew that will not rotate when I will call to shouldAutorotateToInterfaceOrientation , and other subviews will rotate.
and i want to keep the shouldAutorotateToInterfaceOrientation suppot, and not use notification.
thanks
Be sure to define exactly what you mean by having a view "not rotate" when the device is rotated. Rotation can mean several things, depending on which coordinate system to which you refer. A better way to think about it is simply, what do you want your view to look like for each device orientation.
Just to remind, shouldAutorotateTo... is sent to your view controller by the system. You don't invoke it yourself. It doesn't cause rotation. It lets the system ask your view controller what orientations it supports.
Your VC should answer YES for all orientations it supports. A supported orientation is one where the view changes layout in response to a device orientation change, so if any layout change occurs for a given orientation, then the answer to shouldAutorotateTo is probably YES.
Altering subview layout for a given interface orientation is mostly your responsibility. Views have an autoresizingMask which is a bit vector describing some options for sizing and positioning relative to their parent, and this is often adequate. The way to fully control layout on orientation change is by implementing willAnimateRotationToInterfaceOrientation.
For example, here's a fairly permissive shouldAutorotate, enabling all but one orientation...
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
And here's how you would control how subviews layout on rotation...
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
UIView *testView = [self.view viewWithTag:16];
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
// change frames here to make the ui appear according to your spec
// including however you define "not rotating" for each view
self.subviewA.frame = .....
self.subviewB.frame = .....
} else {
self.subviewA.frame = .....
self.subviewB.frame = .....
}
}
If you want one UIView not to Rotate with orientation, one of the easy solution is to add that view to Application top Window like this. Because window dont rotate with device orientations.
[[[[UIApplication sharedApplication]windows]objectAtIndex:0]addSubview:customView];