Somebody else asked the same question in 2018, but he/she did not get answer so hopefully I will have more chance. :-)
Basically, I need my app to have the split view enabled. To do so the target deployment info should be:
All the 'Device orientation' selected
The 'Requires full screen' not selected
To have the possibility to control the orientation, you need the target deployment information to be:
The 'Device orientation' needs at least 'Portrait' OR 'Portrait + Landscape left + Landscape right'
The 'Require full screen' needs or does not need to be selected.
Like you can see, I did not put the 'Upside Down' option from 'Device Orientation' because doing so, I can't lock the orientation for a specific view controller.
If you think it is possible, what am I missing?
Here the code that I am using to lock the orientation based on this:
AppOrientationUtility
struct AppOrientationUtility {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation: UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
And using it:
override func viewDidLoad() {
super.viewDidLoad()
AppOrientationUtility.lockOrientation(UIInterfaceOrientationMask.portrait, andRotateTo: UIInterfaceOrientation.portrait)
}
Related
To make it more clear I have created a demo app with only 2 ViewControllers. The issue only occurs on iPad on which I have kept all orientations enabled which are already enabled by default. So if I present a UIViewController as FormSheet on iPad, the viewWillTransition method is not triggering sometimes when iPad is in landscape mode. I tested it both in simulator as well as on the physical device. Firstly I thought it might be due to my code but in this demo it was clear, if I try to print a statement in viewWillTransition method in my ViewController, it will be printed every time no matter which orientation you are in. However, when I present a FormSheet, it will work for every orientation if iPad is in Portrait mode but if you switch to landscape mode, and try to change app size in Split View, sometimes the statement is not printed. It also happens if you have 2 apps opened in Split Mode with equal ratio and you switch your app to slideOver. So im confused why viewWillTranstion working fine for portrait mode but sometimes will not trigger for landscape mode?
This is my code in ViewController:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
}
#IBAction func button(_ sender: Any) {
let vc = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
if self.traitCollection.userInterfaceIdiom == .phone {
vc.modalPresentationStyle = .fullScreen
}
else {
vc.modalPresentationStyle = .formSheet
}
vc.modalTransitionStyle = .coverVertical
self.present(vc, animated: true, completion: nil)
}
}
And this is code for SecondViewController:
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
DispatchQueue.main.async {
print("Transition Detected")
}
}
}
Here is a video in which you can see the statement is only getting printed in portrait mode: https://youtu.be/iz5iOzOtGCg
It gets printed in landscape mode too but I have included the part ion which viewWillTransition is not triggering.
In Portrait mode, no matter what you do, rotate, change to Split View or drag size of app in Split View, or switch app to slipover, Transition Detected is printed. However in landscape mode, it will not print if you are in Split View and drag to make app small.
This behaviour is so confusing. Any idea how to detect all the transitions in landscape mode too?
My memory is not working for me right now. I think I remember there was a way to prevent the video interface of AVPlayerViewController (or similar) from having the button which allows user to toggle between videoGravity settings, I think those are basically two;
.resizeAspect
.resizeAspectFill
User can also double tap on screen to toggle between these two.
What I'd like to do is force the video of a AVPlayerViewController to only use . .resizeAspect for .videoGravity. I think I remember there should be a way to do this with an easy boolean toggle somewhere, but cannot find it searching for 15 minutes.
Here's a very dirty way I was able to hide the button. Unfortunately, user can still use gestures (double tap, pinch) to zoom the video. The proper thing to do is a full custom view it seems.
e.g.
extension UIView {
var recursiveSubviews: [UIView] {
return subviews + subviews.flatMap { $0.recursiveSubviews }
}
}
class CustomPlayerViewController: AVPlayerViewController {
private func hideZoom() {
let zoomButton = view.recursiveSubviews.first(where: { $0.accessibilityLabel == "Zoom" })
zoomButton?.superview?.removeFromSuperview()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
hideZoom()
}
}
I am working on a mac application, and I like to make initial window be in maximized state, like when you are pressing green button with plus sign.
I don't want it to be full screen.
An app in its zoomed state is not the same thing as "maximized." The green plus icon indicates zoom, which means "the appropriate size for this content." In some applications that's the visible frame (as Eric D. discusses), but it can be almost anything. Try zooming a Safari window for instance.
Assuming you really want "maximized" and not "zoom", then Eric is on the right track, but it can be done better. First, you should use the window's screen if it has one. Also, you should not animate the window resize during launch (since that can look awkward on launch).
func applicationDidFinishLaunching(aNotification: NSNotification) {
if let screen = window.screen ?? NSScreen.mainScreen() {
window.setFrame(screen.visibleFrame, display: true)
}
}
You may want to consider using a NSWindowController to manage this rather than putting it in the application delegate. In that case, you can put this in windowDidLoad. Window controllers are a pretty common tool in AppKit (as opposed to view controllers, which are not historically as common).
If you actually want zoom behavior, familiarize yourself with the the NSWindowDelegate method windowWillUseStandardFrame(_:defaultFrame:). You shouldn't generally call zoom(_:) directly on launch because that will animate, but whatever logic you do in the delegate should be used to compute your frame. Again, make sure to adjust your frame to live on the window's screen if it has one, rather than the main screen.
Ideally, you really should be honoring the last frame that the user used rather than forcing it to the visible frame. That's called frameAutosave in Cocoa if you want to research that more. A window controller will help you manage that somewhat automatically if you just set a autosave name in Interface Builder. (Though it's slightly complicated by needing to compute the frame on first launch to get the visible frame, so it won't be completely automatic.)
Do give some careful thought before making your default frame be the visible frame in any case. That can be really enormous on large monitors (there are still a lot of 30" Cinema displays out there, but even on a 27" it can be pretty overwhelming). Sometimes that's fine depending on your app, but I often find that it's worth defining a maximum initial size (while allowing the user to make it larger).
You can "zoom" a window to the max available space by using NSScreen's visibleFrame as the target frame. Let's say window is your NSWindow IBOutlet:
if let screen = NSScreen.mainScreen() {
window.setFrame(screen.visibleFrame, display: true, animate: true)
}
For example, in the AppDelegate.swift:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(aNotification: NSNotification) {
if let screen = NSScreen.mainScreen() {
window.setFrame(screen.visibleFrame, display: true, animate: true)
}
}
in Swift 4.2:
class ViewController: NSViewController {
override func viewDidAppear() {
super.viewDidAppear()
view.window?.zoom(self) //bespread the screen
//view.window?.toggleFullScreen(self) //fullscreen
}
2020 | SWIFT 5.1:
use extension:
extension NSWindowController {
func maximize() { self.window?.zoom(self) }
}
just call maximize() of NSWindowController instance :)
Swift 5
If anyone's still having issues, trying calling the zoom function the main thread. Worked for me.
DispatchQueue.main.async {
self.view.window?.zoom(self)
}
Hi Guys I really appreciate your help.
I am working on a document based mac application. I put the code you provided in the makeWindowControllers() of Document class and it works like a charm.
Thank you very much. Here is the code I use.
override func makeWindowControllers() {
// Returns the Storyboard that contains your Document window.
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let windowController = storyboard.instantiateControllerWithIdentifier("Document Window Controller") as! NSWindowController
self.addWindowController(windowController)
if let screen = NSScreen.mainScreen() {
windowController.window?.setFrame(screen.visibleFrame, display: true, animate: true)
}
}
this code works well only on single-windowed application, but it's really easy to edit to work with multy-windowed application
usage to maximize and unmaximize window:
TheApp.maximized.toggle()
Source code
public class TheApp {
static var maximized: Bool {
get {
guard let visibleFrame = NSScreen.main?.visibleFrame,
let window = NSApp.mainWindow
else { return false }
return window.frame == visibleFrame
}
set { NSApp.mainWindow?.zoom(newValue) }
}
static var fullscreen: Bool {
get {
guard let screenFrame = NSScreen.main?.frame,
let window = NSApp.mainWindow
else { return false }
return window.frame == screenFrame
} set {
NSApp.mainWindow?.toggleFullScreen(newValue)
}
}
static var mimimized: Bool {
get { NSApp.mainWindow?.isMiniaturized ?? false }
set { NSApp?.mainWindow?.miniaturize(newValue) }
}
}
I am making one custom keyboard in which when orientation of mobile is changed then in landscape i have to hide one button please suggest how can i do this i am using below code to do this task please help me.
in landscape also the button is visible i want to hide button on landscape and visible in portrait mode
override func updateViewConstraints() {
super.updateViewConstraints()
// Add custom view sizing constraints here
var currentDevice: UIDevice = UIDevice.currentDevice()
var orientation: UIDeviceOrientation = currentDevice.orientation
if orientation.isLandscape {
button.hidden = true
}
if orientation.isPortrait {
button.hidden = false
}
}
In your viewDidLoad put
NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationChanged", name: UIDeviceOrientationDidChangeNotification, object: nil)
Then add this method
func orientationChanged()
{
if(UIDeviceOrientationIsLandscape(UIDevice.currentDevice().orientation))
{
button.hidden = true
}
if(UIDeviceOrientationIsPortrait(UIDevice.currentDevice().orientation))
{
button.hidden = false
}
}
Note:
UIDevice.currentDevice().orientation might not give you the correct orientation. You can use the status bar orientation instead UIApplication.sharedApplication().statusBarOrientation
From the apple docs:
UIDevice.currentDevice().orientation
The value of the property is a constant that indicates the current
orientation of the device. This value represents the physical
orientation of the device and may be different from the current
orientation of your application’s user interface. See
“UIDeviceOrientation” for descriptions of the possible values.
I am updating an app that pre-dates iOS5 - consequently much needs to change.
In the existing app we push a view (created in a nib) onto a screen that contains a UIPickerView/UIDatePicker to allow the user to make selections.
I this should be an easy thing to migrate to iOS8/Swift but I have spent the past 24h trying to work out the best way to do this with Storyboard/Segue, Container views, viewControllers, UIPopoverPresentationControler etc. with little consensus (even apple's sample code pushes the UIDatePicker in a table cell)
This seems to be a common requirement and I would appreciate any advice/comments on how others have solved this.
Many thanks
Try this
self.performSegueWithIdentifier("your segue identifier", sender: self)
Thanks for the answers and questions seeking clarification.
After a weekend of investigation I realize that I have been stuck in an old iOS paradigm.
With iOS8 everything has moved to ViewController presentation, allowing the developer to control the animation and define behavior on a range of devices (in different orientations).
The definitive answer to my question (I believe) is as follows:
Use UIPresentationController to present the desired UIViewController:
The view controller is the view to display (use code, nib or storyboard to create the picker view)
The presentation controller manages how it is presented (e.g. size, presentationStyle)
This is also the place to handled transparent/blurred views to cover existing content
check out:
func sizeForChildContentContainer(container: UIContentContainer, withParentContainerSize parentSize: CGSize) -> CGSize
func frameOfPresentedViewInContainerView() -> CGRect
The UIPresentationController needs a transitioning delegate to manage the presentation (conforms to UIViewControllerTransitioningDelegate)
{
// We need to create a presentation controller to manage the presentation of VC
func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController!, sourceViewController source: UIViewController) -> UIPresentationController? {
let opc = OverlayPresentationController(presentedViewController: presented, presentingViewController: source)
return opc
}
// Get hold of an animation controller for presentation
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animationController = OverlayAnimatedTransitioning()
return animationController
}
// Get hold of an animation controller for dismissal
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animationController = OverlayAnimatedTransitioning()
return animationController
}
which in turn requires an animation controller (UIViewControllerAnimatedTransitioning) to handle the animation (slide, dissolve or something more adventurous):
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let containerView = transitionContext.containerView()
let animationDuration = self .transitionDuration(transitionContext)
containerView.addSubview(toViewController.view)
UIView.animateWithDuration(animationDuration, animations: { () -> Void in
<<< Animation Here>>>)
}) { (finished: Bool) in
transitionContext.completeTransition(true)
}
With the building blocks in place, the desired UIViewController may then be called (using an action/selector/segue) and by setting the transitioningDelegate property of the desired view the desired effect will be invoked:
#IBAction func showOVC(sender: AnyObject) {
let ovc = OverlayViewController()
let overlayTransitioningDelegate = OverlayTransitioningDelegate()
ovc.transitioningDelegate = overlayTransitioningDelegate
self.presentViewController(ovc, animated: true, completion: nil)
}
Dismissal is handled in the same way, simply by calling:
dismissViewControllerAnimated(true, completion: nil)
More information can be obtained via the docs and the excellent WWDC video which made everything clear for me: A look inside presentation controllers
or see apple's example code: LookInside
Bottom line - not as simple as before (assuming a single device in one orientation), but for a little more complexity there is far greater control of views and animation, with the ability to scale to multiple devices.
I hope that this helps people in a similar position
Thanks