Why is my view getting offset when changing presentation styles? - ios

I have an iMessage extension and I'm having some issues with changing presentation styles. When I first open the app here is what I get:
That's how it should be. Now when I change to expanded presentation style, this is what I get:
That's also what I want. However, when I switch back to compact, this happens:
Here is my code:
override func didTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
super.didTransition(to: presentationStyle)
presentSearchStickersView()
}
private func presentSearchStickersView() {
let controller = (storyboard?.instantiateViewController(withIdentifier: "SearchStickersViewController"))! as! SearchStickersViewController
controller.view.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
controller.searchDelegate = self
for child in childViewControllers {
child.willMove(toParentViewController: nil)
child.view.removeFromSuperview()
child.removeFromParentViewController()
}
self.addChildViewController(controller)
self.view.addSubview(controller.view)
}
And here is a screenshot of my top constraint:

In my point of view you should not reinstantiate the bar every time you switch to compact or extend mode. You should instantiate it once, then set constraints to the top of the view. I've tried that way and it's working fine ;)
So to sum up, if you are using the storyboard
In your storyboard add your subview to the controller
Set a Top constraint, width equal to superview and centerX to superview
In the code set your search bar (delegate etc) in viewdidload
If you are not using storyboard.
load your xib and add it to your subview (maybe in the didBecomeActive or something like that)
Don't forget to set the translatesAutoresizingMaskIntoConstraints to false
Add the same constraints as above

As RomOne said you should be putting it there once. Style switches should be handled by constraints

Related

CarbonKit CarbonTabSwipeNavigation, Viewcontrollers not aligning correctly

I'm using Carbonkit's CarbonTabSwipeNavigation by ermalkaleci , when the view loads, the viewcontrollers are aligned properly, then when switching to the second viewcontroller the alignment of the second viewcontroller is correct but when scrolling back to the first viewcontroller, it is aligned wrong and a small portion of the second viewcontroller is visible. Tried everything I could find but no luck so far.
My current setup I'm using a toolbar but same behavior when inserting with insertIntoRootViewController :(
class func setupCarbonPages(carbonSwipeTabsItem:CarbonTabSwipeNavigation,tabTitles:[String],totalWidth: CGFloat) -> CarbonTabSwipeNavigation{
// Setup the default style
carbonSwipeTabsItem.toolbar.translucent = false
carbonSwipeTabsItem.setIndicatorColor(AppConfig.BLUECOLOR)
carbonSwipeTabsItem.setNormalColor(AppConfig.BLUECOLOR)
carbonSwipeTabsItem.setSelectedColor(AppConfig.BLUECOLOR, font: UIFont.boldSystemFontOfSize(14))
carbonSwipeTabsItem.setTabExtraWidth(CGFloat(0))
carbonSwipeTabsItem.setNormalColor(AppConfig.BLUECOLOR, font: AppConfig.REGULAR_FONT_16)
carbonSwipeTabsItem.setSelectedColor(AppConfig.BLUECOLOR, font: AppConfig.REGULAR_FONT_16)
// Assign tab width & preload all data
let menuTabItems:UInt = UInt(tabTitles.count)
let tabWidth = (totalWidth / CGFloat(menuTabItems))
for index:UInt in 0 ..< menuTabItems {
carbonSwipeTabsItem.carbonSegmentedControl!.setWidth(tabWidth, forSegmentAtIndex: Int(index))
}
return carbonSwipeTabsItem
}
Then in my viewcontroller :
private func setupSwipableTabs(){
let width = self.view.frame.width
carbonTabSwipeNavigation = CarbonTabSwipeNavigation(items: menuTabTitles as [AnyObject], toolBar: self.toolBar,delegate: self)
Helper.setupCarbonPages(carbonTabSwipeNavigation, tabTitles: menuTabTitles, totalWidth: self.view.frame.width).insertIntoRootViewController(self)
Helper.preloadCarbonPages(carbonTabSwipeNavigation, tabs: 2)
}
Library: https://github.com/ermalkaleci/CarbonKit
(Red line is part of the second viewcontroller)
try to setup the carbon kit code on main thread
in
dispatch_async(dispatch_get_main_queue(), {
let width = self.view.frame.width
carbonTabSwipeNavigation = CarbonTabSwipeNavigation(items: menuTabTitles as [AnyObject], toolBar: self.toolBar,delegate: self)
Helper.setupCarbonPages(carbonTabSwipeNavigation, tabTitles: menuTabTitles, totalWidth: self.view.frame.width).insertIntoRootViewController(self)
Helper.preloadCarbonPages(carbonTabSwipeNavigation, tabs: 2)
})
I Found the problem, for some reason the "Clip Subviews" checkbox was unchecked in the misaligning viewcontroller's storyboard. It appeared to have a negative constraint to the left side what was causing the views to misallign and making the viewcontroller clip all subviews forced all subviews to stay in the main viewcontroller's bounds.
If you are setting up viewcontroller through storyboard.Set the constraint for the container view as
- Align center x to superview
- Equal width to superview(if your container view is equal to superview).
Avoid setting leading and trailing constraint.Removing these two constraint resolved the problem.
you can set width of its segment:
carbonTabSwipeNavigation.carbonSegmentedControl?.setWidth(widthItem, forSegmentAt: 0)

Sliding-In UIView From Bottom

I am trying to build a custom-looking action sheet. I thought the easiest way would be creating a view as a subview and assign constraint of subview's top to superview's bottom. And at the same time assigning a cover view with some opacity. Thus, I could have different versions of subview and I can initialise the necessary one and slide it.
I couldn't find anything useful for Swift, so, using this obj-c answer, I tried to convert it to Swift. I achieved the opaque background with this however translating constraints doesn't seem to work.
var coverView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
coverView.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
coverView.alpha = 1.0
self.view.addSubview(coverView)
self.view.bringSubviewToFront(coverView)
}
//doesn't work
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[coverView]|", options: kNilOptions, metrics: nil, views: NSDictionaryOfVariableBindings(coverView)))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[coverView]|", options: kNilOptions, metrics: nil, views: NSDictionaryOfVariableBindings(coverView)))
I got confused on instantiating the view and applying transition animation. If I choose to create a UIView under ViewController, I cannot adjust constraints to adjust equal width of subview to superview.
How can I use the UIView that I created as a Subview (in Storyboard) and then adjust its width constraints so the UI doesn't bug? Also, how can I apply the transition animation so it seems natural?
This link should be here...
I suggested you use UIView xib file and design your view then load in your view controller.
Ex:
Step 1:
Create xib for view
Step 2:
Set background color black for this view, opacity 62% and Alpha = 1
Step 3:
Take new simple UIView and Design your actual view and set constraint.
For Exp:
In your case set view in bottom.
Step 4:
Load xib in view controller.
class calendarViewController: UIViewController
{
var popUpView: popUpView!
override func viewDidLoad() {
super.viewDidLoad()
bookingConfirmView = NSBundle.mainBundle().loadNibNamed("popUpView", owner: self, options: nil).first as! popUpView
// Set Delegate for xib textField
self.popUpView.Name.delegate = self
self.popUpView.MobileNo.delegate = self
}
}
Step 5:
Add this line to where you want to populate view.
self.view.addSubview(bookingConfirmView)
self.bookingConfirmView.frame = self.view.bounds

Why does the first subview's position of a UIVewController embedded in a UINavigationController get altered?

I've noticed some strange behavior of UITablesViews inside a ViewController when said ViewController is embedded within a UINavigationController. The following is the code of a simple prototype for selecting different UITableViews to be shown in a scene excluding the methods for the TableView and selecting which view to show.
class ChooseTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var tableViewA: UITableView = UITableView()
var tableViewB: UITableView = UITableView()
var colors: [String] = ["red", "blue", "green"]
var shapes: [String] = ["triangle", "circle", "square"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableViewA.frame = CGRectMake(0, 50, 320, 200)
tableViewA.delegate = self
tableViewA.dataSource = self
tableViewA.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cellTableViewA")
tableViewB.frame = CGRectMake(0, 50, 320, 200)
tableViewB.delegate = self
tableViewB.dataSource = self
tableViewB.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cellTableViewB")
self.view.addSubview(tableViewB)
self.view.addSubview(tableViewA)
tableViewB.hidden = true
let segmentSelectorLabels = ["one", "two"]
let segmentSelector = UISegmentedControl(items: segmentSelectorLabels)
segmentSelector.frame = CGRectMake(self.view.frame.width/2 - 50, self.view.frame.height - 100, 100, 40)
self.view.addSubview(segmentSelector)
segmentSelector.selectedSegmentIndex = 0
segmentSelector.addTarget(self, action: "chooseTable:", forControlEvents: .ValueChanged)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
...
}
and here are the results of when ChooseTableViewController is not embedded in a UINavigationController and when it is
Simulators results
In another project the behavior can be fixed by simply adding
let emptyView:UIView = UIView()
and
self.view.addSubiew(emptyView)
with emptyView being the first added subview. This is a hackney solution to having the UITableViews placed in their proper position. Any insight to this behavior is appreciated.
uncheck "Adjust Scroll View Insets" for the viewcontroller in the Attributes inspector or do it in code:
self.automaticallyAdjustsScrollViewInsets = NO; (Obj-C)
self.automaticallyAdjustsScrollViewInsets = false (Swift)
some more explanation:
if this property is set to YES / true the viewcontroller - as the name says - automatically adjusts the insets for the first scrollview in its view hierarchy. this can be helpful if your scrollview / tableview / textview / webview takes up the whole screen and parts of it are normally hidden by the statusbar / navigationbar / tabbar or toolbar. then those insets make your content appear below the top bars / above the bottom bars. this though only happens when the scrollview / ... is the TOP MOST subview in the view hierarchy (= the subview at index 0). to make things clearer i uploaded four examples:
property set to YES / true and two textviews in the view hierarchy (at index 0 and 1): as you can see the insets are only set for the first scrollview
property set to NO / false and two textviews in the view hierarchy (at index 0 and 1): as you can see no insets are set at all
property set to YES / true, a button (subview at index 0) and two textviews (at index 1 and 2): as you can see no insets are set at all although the property is YES / true. that is because no scrollview is the TOP MOST subview in the view hierarchy.
property set to YES / true an a textview in the view hierarchy at index 0 taking up the whole screen: as you can see although the textview takes up the whole screen (starts at 0,0) the text is not hidden by the status- / navigationbar because the viewcontroller automatically adjusted the textview's (scrollview's) inset.
i hope i could help making things a bit clearer. :)
Set table view's frame origin y-positions to 0. Find "Extended Edges" settings for this view controller in storyboard and turn off "Under Top Bars".

Add button on top of UITableViewController (Swift)

I am trying to add a button ontop of a uitableview controller table view. The view controller has a navigation controller and static cells, which is why it is a uitableviewcontroller and not a uiviewcontroller. Now I am trying to add a button at the bottom of the screen that is attached to the navigation controller so that it doesn't scroll with the table view.
I am trying to make something similar to what is below. It has a navigation controller for the top bar, a table view with static cells and then a button, but how did they do the button?
Image: http://postimg.org/image/ilsmqqrip/
Thanks!
UPDATE: How can I use a uiviewcontroller with a tableview with static cells using Swift?
I find Container Views very useful in this scenario! A clean solution and very easy to implement.
Just create a normal UIViewController, add your button and a ContainerView as subviews of this UIViewController (the middle one in the image below). Finally create Embed Segue from ContainerView to your UITableViewController (the one on the right).
This way you can use static cell prototypes, not being limited only to UITableView at the same time.
Result:
there is a better solution for this. you can do this by disabling the Auto Layout(button.translatesAutoresizingMaskIntoConstraints = false) property of the corresponding Button or any UIView for floating button:
Swift 4
//create a button or any UIView and add to subview
let button=UIButton.init(type: .system)
button.setTitle("NEXT", for: .normal)
button.frame.size = CGSize(width: 100, height: 50)
self.view.addSubview(button)
//set constrains
button.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
button.rightAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
} else {
button.rightAnchor.constraint(equalTo: tableView.layoutMarginsGuide.rightAnchor, constant: 0).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.layoutMarginsGuide.bottomAnchor, constant: -10).isActive = true
}
I did something similar with UITableViewController and a static datasource. I added the button in the footerview of my tableview.
To make it align to the bottom of the screen i needed this code in my viewcontroller:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Make footerview so it fill up size of the screen
// The button is aligned to bottom of the footerview
// using autolayout constraints
self.tableView.tableFooterView = nil
self.footerView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.tableView.frame.size.height - self.tableView.contentSize.height - self.footerView.frame.size.height)
self.tableView.tableFooterView = self.footerView
}
In short, I resize the footerview to take up all the remaining space after the contentsize of the table view is removed. Since the button is aligned to the bottom of the footerView with autolayout, it will stay in the bottom of the screen.
The Storyboard:
Here is the result:
The UITableViewController will take up the whole space, so you won't be able to add the button. Refactor your UITableViewController based code into UIViewController with UITableView manually added. This way you will be able to set the size of your table view and put the button to the bottom.
Unfortunately UITableViewController has a tableView as its top level view. Of course if you look in the view debugger you can see that the tableview is not the root view. Therefore you can add the buttons to the tableView's window programatically. If you have to do it after the fact, this is probably the easiest way to add a top level element over a UITableViewController. Otherwise if you are doing it in the initial design, you can use container view for your buttons and a UITableViewController for the TableView. The downside of this approach is you end up with two view controllers, one for the container and one for the table and its often necessary to pass information back and for between them. If you are using swift you can simplify this by nesting the tableViewcontroller inside the container view controller class.
If you want to add a button to the window, you can do this lazily once you are sure that the view has a window. Note that the buttons belong to the window and not to the view controller, so its your responsibility to remove them when the view controller disappears.
private weak var button: UIButton!
...
override func didMove(toParentViewController parent: UIViewController?) {
super.didMove(toParentViewController: parent)
guard self.button == nil, let window = tableView.window else {
return
}
let button = UIButton(frame: CGRect(x:0, y:40, width: 200, height: 20))
button.setTitle("This is a red button", for: .normal)
button.backgroundColor = UIColor.red
window.addSubview(button)
self.button = button
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
button?.removeFromSuperview()
}
Step 1 :-
Drag and drop one uiview to UITable View Controller (Static)
Automatically it sticks to the bottom.
If you need to, you can also add two buttons inside UIView... It depends on your requirements.
Step 2 :-
Connect the outlet for uiview (outletView)
Step 3 :-
Add this below code in View Will Appear.
override func viewWillAppear(_ animated: Bool) {
outletViewBottom.backgroundColor = .red
tableView.addSubview(outletViewBottom)
// set position
outletView.translatesAutoresizingMaskIntoConstraints = false
outletView.leftAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.leftAnchor).isActive = true
outletView.rightAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.rightAnchor).isActive = true
outletView.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor).isActive = true
outletView.widthAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.widthAnchor).isActive = true
outletView.heightAnchor.constraint(equalToConstant: 50).isActive = true // specify the height of the view
}
Step 4 :-
Now run the code... Happy coding.
all you need to do is to add your Top view whichever it is to the navigationController.view like so:
self.navigationController?.view.addSubview(YOUR_TOP_VIEW)
so if you need a sticky button/view etc... on top of TableViewController which does not scroll with tableView, use this approach.
Here is a UIViewController, with a UITableView added as a subview. At the top right, you can see a dropdown that says Content: Dynamic Prototypes. Change it to Static Cells.

TableView Showing Behind Tab Bar

I am updating my app to use iOS 7 and I'm having a problem with a table view. My tab bar is translucent. The problem is when I scroll to the bottom of my table view, part of the last cell is still behind the tab bar. I'd like to have a bit of space between the last cell and the tab bar. I could fix this by using an opaque tab bar instead, but I want to keep it translucent.
Try setting
self.edgesForExtendedLayout = UIRectEdgeNone;
self.extendedLayoutIncludesOpaqueBars = NO;
self.automaticallyAdjustsScrollViewInsets = NO;
Inside the tableview controller
Swift 4.x
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(0, 0, self.tabBarController!.tabBar.frame.height, 0)
self.yourTableView.contentInset = adjustForTabbarInsets
self.yourTableView.scrollIndicatorInsets = adjustForTabbarInsets
Check the screen shot
Check the under top Bar and Un-checke under Bottom Bar
SWIFT 3
put this inside viewDidLoad of your tableViewController:
self.edgesForExtendedLayout = UIRectEdge()
self.extendedLayoutIncludesOpaqueBars = false
self.automaticallyAdjustsScrollViewInsets = false
Swift 3.0
This is what worked for me. In your Custom ViewController:
override func viewDidLoad() {
super.viewDidLoad()
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(self.tabBarController!.tabBar.frame.height, 0, 0, 0);
//Where tableview is the IBOutlet for your storyboard tableview.
self.tableView.contentInset = adjustForTabbarInsets;
self.tableView.scrollIndicatorInsets = adjustForTabbarInsets;
}
Not to sure I like the solution but it works for me.
With iOS 11 I have no issue, I simply use the following in viewDidLoad():
self.collectionView.bottomAnchor.constraint(self.view.safeAreaLayoutGuide.bottomAnchor).isActive = true
However on iOS 10 I need to hack my way like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let tabBarHeight: CGFloat = (self.parent?.tabBarController?.tabBar.frame.size.height)!
if #available(iOS 11.0, *) {
} else {
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -tabBarHeight).isActive = true
}
}
This is working for me
override func viewDidLoad() {
self.edgesForExtendedLayout = UIRectEdge()
self.extendedLayoutIncludesOpaqueBars = false
}
If any view shows behind a UITabBar you can grab the bottomLayoutGuide and make adjustments at runtime. What I do is have a BaseViewController that all my view controllers inherit from. Then if the tab bar is visible we adjust the view like so:
import UIKit
class BaseVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
//Ensures that views are not underneath the tab bar
if tabBarController?.tabBar.hidden == false {
var viewBounds = self.view.bounds;
var bottomBarOffset = self.bottomLayoutGuide.length;
self.view.frame = CGRectMake(0, 0, viewBounds.width, viewBounds.height - bottomBarOffset)
}
}
}
Since I don't use storyboards (where you can click a checkbox in IB to fix this problem), this has been the best solution I have found.
It is really hard to resolve the issue without detail information or actual codes. I have similar issue of tabview behind UItabBar in my project. The solutions offered here do not work in my case. After exploring my codes, I found a solution for my case.
Here is brief explanation of my case. I have a UItabBar in main view with two tab buttons. In one tab view, there is table view. If user taps on a row, a detail view is presented by using navigation controller. In the detail view, the tab bar is hidden, and a toolbar is showing at the bottom.
In order to bring tab bar back and hide the toolbar when the main view is brought back, I have to explicitly show tab bar and hide toolbar in the event of viewWillAppear:
class myMainViewController: UITableViewController {
private var tabBarHidden: Bool? = {
didSet {
self.tabBarController?.tabBar.isHidden = tabBarIsHidden ?? true
}
}
private var toolBarIsHidden: Bool? {
didSet {
let hidden = toolBarIsHidden ?? true
self.navigationController?.toolbar.isHidden = hidden
self.navigationController?.setToolbarHidden(hidden, animated: true)
}
}
...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarIsHidden = false
self.toolBarIsHidden = true
}
...
}
I finally realize that the visibility of bar at the bottom is set in the event of viewWillAppear. At that time, the tableView or scroll view's content insets are set already based on no bar at the bottom. That's why my tableView is behind the bottom bar.
The solution I found is to reset content insets in the event of viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
// In the event of viewWillAppear, visibilities of tool bar and tab bar are set or changed,
// The following codes resets scroll view's content insets for tableview
let topInset = self.navigationController!.navigationBar.frame.origin.y +
self.navigationController!.navigationBar.frame.height
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(
topInset, 0,
self.tabBarController!.tabBar.frame.height, 0)
self.tableView.contentInset = adjustForTabbarInsets
self.tableView.scrollIndicatorInsets = adjustForTabbarInsets
}
The best approch would be to Embed TabBarController to your ViewController (Editor -> Embed In -> TabBar Controller)and set the bottom of the tableview to be bottom of safe area of viewcontroller. The other ways wont be as perfect as this one.
You need to adjust the height of the table view. Just leave 49px at the bottom, as the tabbar height is 49 px. Adjust the height of table view so that it leaves 49px space below it.

Resources