Running tabBarItem code before view controller loads - ios

I have a tabbar app, and I put the following code in my view controllers:
let orientation = UIDevice.current.orientation
if orientation == .portrait {
self.tabBarItem.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -17.0)
} else if orientation == .landscapeLeft || orientation == .landscapeRight {
self.tabBarItem.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: 0.0) // updating the title positon
}
This changes the offsets for my tab bar items' titles. In landscape, the offsets get reversed automatically, so I have to do it manually. This works, but only when you actually click on a tab bar item and it loads a view controller.
I want to load this code at the start, so it changes the offset when you launch the app (before the view controllers load). Here are some pictures to describe what I said:
When you first launch the app, this is the first view controller, so it loads that code and it changes position:
If you tap on the second item, the second view controller loads and it changes position as well.
I need to load this code for each tab bar item, so it looks like this from the start:

You should subclass UITabBarController and execute the code that changes the title position adjustment after the controller loads (viewDidLoad) and every time the view size/orientation changes (viewWillTransition).
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
tabBarItemsUI()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { [weak self] context in
self?.tabBarItemsUI()
})
}
fileprivate func tabBarItemsUI() {
let isLandscape = [.landscapeLeft, .landscapeRight].contains(UIDevice.current.orientation)
tabBar.items?.forEach { $0.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: isLandscape ? 0 : -17) }
}
}

Try running this code in viewWillAppear() function.

Related

Prevent UIView with AVCaptureVideoPreviewLayer from rotating with rest of ViewController

I have a view controller, embedded in a tab bar controller, that, among other things, presents a AVCaptureVideoPreviewLayer in a UIView.
When the device is rotated, I want the view controller to rotate with it- except for the aforementioned UIView.
Unlike this related question, however, I am not just rotating/transforming my other views in the view controller. The other views need to use their configured autolayout rotation behavior.
I've tried several things, including simply setting the video orientation to portrait:
previewLayer.connection.videoOrientation = .portrait
to extracting the UIView to a separate view controller, embedding that view controller into the original view controller, and setting its autoRotation properties
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
but then I learned here that iOS only looks at the top-level view controller for those properties.
With everything I have tried, the video preview is rotating with the rest of the view controller- ending up sideways.
The only thing that works, but is hacky and sometimes causes the video preview to become misaligned, is this
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if let videoPreviewLayerConnection = previewLayer.connection {
if let newVideoOrientation = AVCaptureVideoOrientation(rawValue: UIApplication.shared.statusBarOrientation.rawValue) {
videoPreviewLayerConnection.videoOrientation = newVideoOrientation
}
}
}
}
I basically need the opposite of this question.
How can I force the video preview to not rotate but also allow the rest of the view controller to rotate normally? (Same behavior as iOS Camera app except that the other UI elements rotate normally instead of the 90° rotation transform)
The following is possibly as hacky as your solution but it looks cleaner visually.
In viewWillTransition I set the affine transform of the previewView to counteract the orientation set by rotating the phone. It looks cleaner than just setting the videoOrientation as the affine transform animates at the same speed as the orientation change. It is done as follows.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let orientation = UIDevice.current.orientation
var rotation : CGFloat = self.previewView.transform.rotation
switch(orientation) {
case .portrait:
rotation = 0.0
case .portraitUpsideDown:
rotation = CGFloat.pi
case .landscapeLeft:
rotation = -CGFloat.pi/2.0
case .landscapeRight:
rotation = CGFloat.pi/2.0
default:
break
}
let xScale = self.previewView.transform.xScale
let yScale = self.previewView.transform.yScale
self.previewView.transform = CGAffineTransform(scaleX:xScale, y:yScale).rotated(by:rotation)
self.view.layoutIfNeeded()
}
Here is the extension to CGAffineTransform the code above uses
extension CGAffineTransform {
public var xScale: CGFloat {
get {return sqrt(self.a * self.a + self.c * self.c) }
}
public var yScale: CGFloat {
get {return sqrt(self.b * self.b + self.d * self.d) }
}
public var rotation: CGFloat {
get {return CGFloat(atan2f(Float(self.b), Float(self.a))) }
}
}

Swift: Changing view/screen depending on device orientation. What is "efficiency wise" the better option?

Introduction
I'm creating a calendar app in which one of the screens has a landscape view and a portrait view. For simplicity picture the iOS apple calendar in which the landscape view is a week view (i.e. completely different than the portrait view).
Issue
I'm getting a sense of bad code structure and potential loss of efficiency in my current code. Since I basically use the users battery and CPU for the week view concurrently with the portrait view even though not everyone uses the week view. What is the better practice in implementing a different presentation depending on device rotation?
Question
Which pattern would be the more efficient approach? Is there a third option I haven't considered that would result in better performance?
My attempts
(I've also included a code example (below) that shows my implementation of these attempts in code.)
Two UIViewControllers that is segued and "popped" depending on conditions of device orientation in viewWillTransition(). Although that became quickly out of hand since the method triggers in all view controller currently in memory/navigationStack, resulting in additional copies of viewControllers in the navigation stack if you swap between right landscape and left landscape.
Using one UIViewController and two UIView subclass that is initialized and communicating to the view controller through the delegate-protocol pattern. In which during the viewWillTransition() I simply animate an alpha change between the two UIViews depending on the device orientation.
Code example
(I have provided two simplification to illustrate my attempts described above, methods such as dataSource and delegate methods for UICollectionViews are not included are not included in the example below.)
Attempt 1:
class PortraitCalendar: UIViewController {
let portraitCalendarView : MonthCalendar = {
// Setup of my portrait calendar, it is a UICollectionView subclass.
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(portraitCalendarView)
// Additional setup..
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isLandscape {
performSegue(withIdentifier: "toLandscapeCalendar", sender: nil)
}
}
}
class LandscapeCalendar: UIViewController {
let landscapeView : LandscapeView = {
// Setup of the landscape view, a UICollectionView subclass.
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(landscapeView)
// Additional setup..
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isPortrait {
navigationController?.popViewController(animated: true)
}
}
}
Attempt 2:
class PortraitCalendar: UIViewController, LandscapeCalendarDelegate {
let portraitCalendarView : MonthCalendar = {
// Setup of my portrait calendar, it is a UICollectionView subclass.
}
// UIView subclass with a UICollectionView within it as a week calendar.
let landscapeCalendar = LandscapeView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(portraitCalendarView)
view.addSubview(landscapeCalendar)
landscapeCalendar.alpha = 0
portraitCalendarView.alpha = 1
// Constraints and additional setup as well of course.
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isLandscape {
navigationController?.isToolbarHidden = true
self.view.layoutIfNeeded()
landscapeCalendarDelegate?.splitCalendarViewWillAppear()
UIView.animate(withDuration: 0.1) {
self.portraitCalendarView.alpha = 0
self.landscapeCalendar.alpha = 1
}
} else {
self.portraitCalendarView.alpha = 1
self.landscapeCalendar.alpha = 0
}
}
}
Thanks for reading my question.
I'd definitely go for an option number 2.
That way you encapsulate all the logic related to the calendar, for example for adding event or displaying it, in one view controller, without the need to reimplement the sane logic somewhere else (eg other view controller with landscape mode). Having two views for a different layout modes is not THAT easy to maintain, but if that's the only way to show the difference between the modes it really is a fine solution. And it's much easier to maintain than two view controllers with the very similar logic.

UIView.isHidden not working when changing orientation using viewWillTransition

I'm trying to change orientation with just one view, the rest are anchored to Portrait. I've set a method in my AppDelegate as below
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if globalVariables.gIsDosageView == "Y" {
if UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight {
return UIInterfaceOrientationMask.all;
} else {
return UIInterfaceOrientationMask.portrait;
}
} else {
return UIInterfaceOrientationMask.portrait;
}
}
The global variable gIsDosageView is set to "Y" when the Dosage view is selected. I've created two separate views, portraitView (375x812) and landscapeView (812x375). I've used the viewWillTransition method to catch each change in orientation as below
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if size.width < 400 {
self.userImg.isHidden = false
self.deviceImg.isHidden = false
self.portraitView.isHidden = false
self.landscapeView.isHidden = true
}
else {
self.userImg.isHidden = true
self.deviceImg.isHidden = true
self.portraitView.isHidden = true
self.landscapeView.isHidden = false
}
}
When I go from the main screen to Dosage it displays the correct screen and will toggle between both orientation views without issue. However, if I go to another screen and come back to the Dosage screen it only shows the screen that was first loaded in both orientation screens. I've stepped through the code and it hides the right screens but this is not reflected in the resulting view. If I select the screen in portrait first, it will toggle successfully between portrait and landscape but if I go to the next screen and return to Dosage, it will only show the Portrait screen regardless of orientation and appears to ignore the code in viewWillTransition().
Why is this and what have I missed?
viewWillTransition is only called when the device is rotated. When you switch between open views without rotating, it won't get called, so won't set the view up how you want it. Try putting the test in viewWillAppear instead.

Swift 3 -Flip entire screen/view On Orientation change

I need to a way to constantly monitor if the user has rotated the iPad.
UserDidRotate() {
if(orientation = portrait.upsideDown){
//
//Code that will Present View upside down...
//
}
else if(orientation = portrait){
//
//Code that will Present View right side up...
//
}
}
How can I check for orientation change and also manually present the view upside down for my Swift 3 app?
EDIT:
I have tried:
override func viewWillTransition(to size: CGSize, with coordinator:
UIViewControllerTransitionCoordinator){}
and that method is never hit.
Try:
self.view.transform = self.view.transform.rotated(by: CGFloat(M_PI))

Different size classes for iPad portrait and landscape modes with containerviews

I've found this question and I've tried to implement the solution that has been given. However I run into a problem.
My initial view controller has two container views who both have their own view controller. I've created a root view controller that is assigned to the initial view controller. The code in this class looks like this.
class RootViewController: UIViewController {
var willTransitionToPortrait: Bool!
var traitCollection_CompactRegular: UITraitCollection!
var traitCollection_AnyAny: UITraitCollection!
override func viewDidLoad() {
super.viewDidLoad()
setupReferenceSizeClasses()
// Do any additional setup after loading the view.
}
override func viewWillAppear(animated: Bool) {
willTransitionToPortrait = self.view.frame.size.height > self.view.frame.size.width
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
willTransitionToPortrait = size.height > size.width
}
func setupReferenceSizeClasses(){
let traitCollection_hCompact = UITraitCollection(horizontalSizeClass: .Compact)
let traitCollection_vRegular = UITraitCollection(verticalSizeClass: .Regular)
traitCollection_CompactRegular = UITraitCollection(traitsFromCollections: [traitCollection_hCompact, traitCollection_vRegular])
let traitCollection_hAny = UITraitCollection(horizontalSizeClass: .Unspecified)
let traitCollection_vAny = UITraitCollection(verticalSizeClass: .Unspecified)
traitCollection_AnyAny = UITraitCollection(traitsFromCollections: [traitCollection_hAny, traitCollection_vAny])
}
override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection? {
let traitCollectionForOverride = ((willTransitionToPortrait) != nil) ? traitCollection_CompactRegular : traitCollection_AnyAny
return traitCollectionForOverride;
}
However when I run it the size class won't respons like it should. One of the container view controllers will start acting weird in both landscape and portrait mode like can be seen below.
When I don't assign the rootviewcontroller it will look like this
While it should look like this in portrait mode
Does anyone know what might be going wrong here? Why it doesn't change the size class like desired.
EDIT
Like #Muhammad Yawar Ali asked here are screenshots from the position of all the size classes I've set. I have no warnings or errors on any constraints so these screenshots contain the updated views.
I hope this shows everything that is needed.
EDIT:
for some reason I'm unable to put in all the screenshots
On the viewWillTransitionToSize you need to call also super, to pass the event to the next responder (your rootviewcontroller)...
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
willTransitionToPortrait = size.height > size.width
}
Realize this is over two years old but...
I just ran across what I think is a similar issue. What you may be forgetting is that 'overrideTraitCollectionForChildViewController' only overrides the views children, so this method won't do anything with the containers since they are located at the root.
I solved this putting my two containers in a UIStackView in Interface Builder and made a property of this stack in code and then updated the axis depending on the orientation. For example, in Objective-C:
#property (weak, nonatomic) IBOutlet UIStackView *rootStack;
// ...
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
return [super overrideTraitCollectionForChildViewController:childViewController];
}
if (CGRectGetWidth(self.view.bounds) < CGRectGetHeight(self.view.bounds)) {
self.rootStack.axis = UILayoutConstraintAxisVertical;
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
}
else {
self.rootStack.axis = UILayoutConstraintAxisHorizontal;
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
}
If you have any constraints that are different between portrait and landscape you will need to adjust those in code as well.
I suppose you could also solve this by embedding the view controller with the containers in another view controller.
I have cloned your code from repository : https://github.com/MaikoHermans/sizeClasses.git
And editted code put the below code in you controller it will work fine & will not effect your design in iPads.
import UIKit
class RootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection? {
if view.bounds.width < view.bounds.height {
return UITraitCollection(horizontalSizeClass: .Unspecified)
} else {
return UITraitCollection(horizontalSizeClass: .Regular)
}
}
}
You can try with this code but There is an issue i believe its not updating traits properly for ipads and view layout remains same but looks good. I have tried multiple ways but not succeeded yet will update my answer.

Resources