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

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. šŸŽ

Related

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

Flickering of ViewController where textField is first responder

I faced with a flash of viewController, where searchField is first responder, when I am returning to that controller. i tried i on ios9 , but there is no such a problem, so it looks that it is caused in ios10.
Could you please advice what could be a reason of it and how to solve it?
Thank you in advance for any suggestion.
EDIT:
My View hierarchy is quite simple(its just a demo), but cause the issue happends here as well i am investigating it here. The code looks like that:
class ViewController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(_ animated: Bool) {
searchBar.becomeFirstResponder()
}
}
I faced this problem too. In my case, the screen on the first page was a stack view inside of a scroll view and I'm still not sure why this was happening, but setting the content inset of the scrollview in viewWillAppear fixed this issue for me. I used this code for that:
let insets = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0)
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
scrollView.contentSize = CGSize(width: stackView.frame.width, height: stackView.frame.height)
If this type of solution won't work for you, could you add some more details about the hierarchy of objects on your view?
Maybe setting the contentInset of the UISearchBarController would fix this problem.

Swift / cornerRadius and masksToBounds lets UIImageView disappear

I have a UITableViewController with a UIImageView. The content is loaded with the following simple code.
#IBOutlet weak var avatarImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
func updateUI() {
backendless.userService.currentUser.retrieveProperties()
avatarImageView.layer.cornerRadius = avatarImageView.frame.size.width/2
avatarImageView.layer.masksToBounds = true
let imgURL = NSURL(string: (backendless.userService.currentUser.getProperty("Avatar") as? String)!)
avatarImageView.sd_setImageWithURL(imgURL)
tableView.reloadData()
}
The ImageView does not appear.
But if I delete the following code, the UIImageView will be set and displayed correctly.
avatarImageView.layer.cornerRadius = avatarImageView.frame.size.width/2
avatarImageView.layer.masksToBounds = true
What am I missing? Help is very appreciated.:
PS:
Xcode 8.0
Swift 2.3
Contrains for ImageView are set to 120*120
sd_setImageWithURL = pod 'SDWebImage'
Try this:
override func viewDidLoad() {
super.viewDidLoad()
self.view.layoutIfNeeded() // <- This will update your initial view frames
updateUI()
}
What is happening is that in Xcode 8.0, by the time you hit viewDidLoad, the frame of all subviews of a controller is still x: 0.0, y: 0.0, w: 1000.0, h: 1000.0, so in effect you're making the corner radius of your avatar image view 500.0pt. When you call layout if needed, the correct frames will be calculated, and give you the value you're expecting.
One obvious problem is that you are calling updateUI too soon. In viewDidLoad, there is no UI. The view has loaded but that is all; it is not in the interface and no layout has yet taken place. Your code depends upon avatarImageView.frame, but we do not yet know what that is. You have constraints on the image view, but they have not yet taken effect! Thus you should postpone this call until after the interface is present, e.g. viewDidAppear or at least viewDidLayoutSubviews. But of course then you probably only want to call it once (typed in browser, not tested, but it will be something like this):
var didConfigureUI = false
func viewDidLayoutSubviews() {
if !didConfigureUI {
didConfigureUI = true
updateUI()
}
}
I think your problem with your controller. You have added the image view to a UITableViewController. That means constraint set to image view will not work if you use the frame in view did load. You need to change frame of the UIIMageView before use it or you need to use UIViewController. use the below code before using the frame of image view.
avatarImageView.frame.size.width = 120.0
avatarImageView.frame.size.height = 120.0
I think this will solve the problem. But you should use UIViewController if there is no special purpose of UITableViewController.
ā€œxy.view.layoutIfNeeded()ā€
needs to be called everytime iā€™m calling a property before the view has finished loading
for example imageView.view.layoutIfNeeded() to call for the height and widths values

Resources