UIModalPresentationFormSheet with transparent background? - ios

Essentially here I want the modal form sheet to be transparent so I can see the UIView behind it (it's fullscreen).
Is this possible? Setting the UIView to not opaque and a backgroundColor of [UIColor clearColor] doesn't work.
I've noticed when I switch from UIModalPresentationFormSheet to another presentation style my view's background is indeed transparent.
Where have I gone wrong?

This worked for me:
[modalView.superview setBackgroundcolor:[UIColor clearColor]];
It'll still dim the background view transparently, but won't cover it with solid gray.
...............................................
UPDATE FOR iOS 8
My previous solution no longer works for iOS 8.
There are a lot of steps to get something like this to work for iOS 8. So I've created a nice reusable solution: ModalController
https://github.com/koreyhinton/ModalController.
It's real easy to use and auto-dismisses itself.
let modal = ModalController()
modal.content = myCustomView
presentViewController(modal, animated: true, completion: nil)
If you are interested in implementation details, this is how I did it:
Make your modal view controller take up the whole screen and the main view's background will be toggled between clear and semi-dark. You will then have a content view that will be the modal which will be centered with a solid background color. I also use presentation style: .OverCurrentContext which I assign in the init method.
var content = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.clearColor()
content.frame = CGRect(x:0, y:0, width:300, height:300)
content.backgroundColor = UIColor.blackColor()
view.addSubview(content)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.7)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
view.backgroundColor = UIColor.clearColor()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
content!.center = view.center
}

As far as I can tell this is no longer possible. It used to be but Apple has disabled this functionality.
What I've done instead is to just create my UIViews like normal and add them as subviews to my main view. I have to dismiss my form sheet and then do this presentation.

Related

Swift - Interactive dismissing form sheet doesn't work iOS 13

I'm presenting my UIViewController with modalPresentationStyle = .formSheet. Everything works as expected except interactive dismissing this controller (I drag form sheet from the top down). Controller is dismissed but it's presentingViewController doesn't move back. It remains stucked with black edges around.
If I dismiss presented controller using dismiss(animated:completion:), presentingViewController moves back as expected so it seems it doesn't work just when user dismiss it by dragging.
If you need any other details let me know. Thank you. 🙏
I'm using iOS 13.3 and Xcode 11.2.1.
Update:
I found a problem. 🎉 This view which I presented contained UITableView. I was using my method to set its tableFooterView:
extension UITableView {
func setFooterView(_ view: UIView) {
view.frame = bounds
view.setNeedsLayout()
view.layoutIfNeeded()
let height = view.systemLayoutSizeFitting(
CGSize(width: view.frame.width, height: UIView.layoutFittingCompressedSize.height)
).height
var frame = view.frame
frame.size.height = height
view.frame = frame
tableFooterView = view
sectionFooterHeight = 0
}
}
...and I called this method in my view controller in viewDidLayoutSubviews() since I thought that this is where I should set this footer view (Simply because we were using it like this in various projects 🤷🏻‍♂️):
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.setFooterView(footerView)
}
But I wasn't right. Simply calling it in viewDidLoad() resolved it.
override func viewDidLoad() {
super.viewDidLoad()
tableView.setFooterView(footerView)
}
No the question is: why?
If anyone could provide correct explanation why this happens I would be grateful and I would reward him/her with bounty. 🎁

iOS 13 remove UIView for simulate status bar background

I'm working with a UITableViewController which when scrolling makes the navigationBar disappear. Now when the navigation bar is hidden when the user swipes the table view the contents of the cells are seen below the status bar ...
To solve this problem I tried to insert a UIView to simulate a background of the status bar and everything works but the problem is that when I close the UITableViewController the background view of the status bar is not removed from the superview
For now my code is this, can you help me understand where I am wrong? why can't I remove the UIView from the superview?
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupStatusBarView()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.isHidden = true
UIApplication.shared.windows.first?.viewWithTag(1)?.removeFromSuperview()
}
//MARK: - Setup Status Bar View
func setupStatusBarView() {
let height = view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
let statusBarView = UIView()
statusBarView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height:height+5)
statusBarView.backgroundColor = .systemBackground
statusBarView.tag = 1
UIApplication.shared.windows.first?.addSubview(statusBarView)
}
viewDidLayoutSubviews get calls multiple times and you have put setupStatusBarView() in viewDidLayoutSubviews that means your background view has been added multiple times and this is totally wrong flow!
You are removing topmost view only not previous ones!
You should set frame in viewDidLayoutSubviews and should add the view from viewDidLoad!
try this one
let subviewArray = UIApplication.shared.windows.first?.subviews
for view in subviewArray!{
if view.tag == 1{
view.removeFromSuperview()
}
}

iOS 13 UIViewController modal presentation shadow

I have modaly presented view controller in iOS >=13. Root view has clear background:
view.backgroundColor = .clear
Child view with white background has some top offset like this:
All is ok, but when I try to dismiss it by swipe down I see slightly visible shadow of presented view controller:
Is it posible to remove this shadow on modal presentation?
UPDATE: Upon further investigation, this does not appear to be something that can be changed. It's a private UIKit View setup by iOS and is a new addition in iOS 13. See 19:50 at https://developer.apple.com/videos/play/wwdc2019/224/
For my own apps/games I'll be looking to create a custom UIModalPresentationStyle to achieve the look I want.
You can also alleviate from this by simply presenting as .fullScreen or another presentation style instead of this new sheet method.
I have been trying to find the answer to this myself. So far I have only found that by setting the layer.shadowColor to clear it fixes this but only on iPhone. I cannot find how to fix this on iPad.
override func viewDidLoad() {
view.layer.shadowColor = UIColor.clear.cgColor
}
I have solution for you
extension UIViewController {
func removeBackgroundForParents() {
var superview = view.superview
while superview != nil {
superview?.layer.backgroundColor = UIColor.clear.cgColor
superview?.layer.shadowColor = UIColor.clear.cgColor
superview = superview?.superview
}
}
}
And use it in your view controller.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
removeBackgroundForParents()
}

UISplitViewController: titleView in DetailViewController disappears on landscape orientation, intended behaviour?

I'm adding a custom titleView inside a navigation bar using navigationItem.titleView for both Master/Detail VC. On changing the orientation of the device to landscape, titleView under MasterViewController works fine, but for DetailViewController titleView disappears. On changing the orientation back to portrait titleView appears back for DetailViewController. I have also attached a link for source code and video.
Is this an intended behavior or am I making a mistake from my side or is it an issue from Apple's side ?
//Custom Title View:
class TitleView: UIView {
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: 50, height: 20)
}
}
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Adding titleView for Master/Detail VC:
navigationItem.titleView = {
//Setting frame size here, did not make any difference
let view = TitleView(frame: .zero)
view.backgroundColor = .red
return view
}()
}
}
Full source code here: https://github.com/avitron01/SplitViewControllerIssue/tree/master/MathMonsters
Video highlighting the issue:
https://vimeo.com/336288580
I had the same issue. It seems an iOS bug. My workaround was to reassign the title view on every view layout. I used this piece of code in my DetailViewController:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let v = navigationItem.titleView {
navigationItem.titleView = nil
navigationItem.titleView = v
}
}
For those who stumble upon this, see also iOS 11 navigationItem.titleView Width Not Set. Basically, there's two suggested workarounds:
use a custom UIView that tells iOS to treat its intrinsicContentSize to be as big as possible with UIView.layoutFittingExpandedSize
use widthAnchor/heightAnchor constraints to set width and height of your view

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