Swift: Make large title start small or big everytime - ios

I am currently working on a projekt in swift where I use a navigation controller to segue between different views. My projekts title is displayed in the navigation bar after the segue with "prefer large titles" on and it displays automatic. It all works as it should, but for some reason the title is not displayed the same way after the segue at the different views. When I segue to a viewcontroller with a scrollview the title is displayed like this to begin with
And when I segue to a tableviewcontroller the title is displayed like this
Do you know why it is displayed differently? I do not care how the title is displayed to begin with, just that it is in the same way every time. You can see the problem in this link
https://github.com/Rawchris/small-title

Here is the solution to your problem.. Thanks for sharing the repo.
This declaration is from your ViewController. In addition to this, as you are adding the components programmatically so i would suggest to mark translatesAutoresizingMaskIntoConstraints to be false
lazy var scrollView: UIScrollView = {
let view = UIScrollView(frame: .zero)
view.backgroundColor = .white
view.frame = CGRect(x: self.view.bounds.minX, y: CGFloat(0.0), width: self.view.bounds.width, height: self.view.bounds.height)
view.contentSize = contentViewSize
return view
}()

Related

Swift Presenting Controller Dismiss Bar Indicator

This should be an easy question for most of you. Presenting view controllers like in the attached photo now have a bar at the top of them (see red arrow) to indicate that the user can swipe down to dismiss the controller. Please help with any of the following questions:
What is the proper term for this icon?
Is it part of swift's ui tools / library or is it just a UIImage?
Can someone provide a simple snippet on how to implement - perhaps it is something similar to the code below
let sampleController = SampleController()
sampleController.POSSIBLE_OPTION_TO_SHOW_BAR_ICON = true
present(sampleController, animated: true, completion: nil)
Please see the red arrow for the icon that I am referring to
grabber
grabber is a small horizontal indicator that can appear at the top edge of a
sheet.
In general, include a grabber in a resizable sheet. A grabber shows people that they can drag the sheet to resize it; they can also
tap it to cycle through the detents. In addition to providing a visual
indicator of resizability, a grabber also works with VoiceOver so
people can resize the sheet without seeing the screen. For developer
guidance, see prefersGrabberVisible.
https://developer.apple.com/design/human-interface-guidelines/ios/views/sheets/
From iOS 15+ UISheetPresentationController has property prefersGrabberVisible
https://developer.apple.com/documentation/uikit/uisheetpresentationcontroller/3801906-prefersgrabbervisible
A grabber is a visual affordance that indicates that a sheet is
resizable. Showing a grabber may be useful when it isn't apparent that
a sheet can resize or when the sheet can't dismiss interactively.
Set this value to true for the system to draw a grabber in the
standard system-defined location. The system automatically hides the
grabber at appropriate times, like when the sheet is full screen in a
compact-height size class or when another sheet presents on top of it.
Playground snippet for iOS 15:
import UIKit
import PlaygroundSupport
let viewController = UIViewController()
viewController.view.frame = CGRect(x: 0, y: 0, width: 380, height: 800)
viewController.view.backgroundColor = .white
PlaygroundPage.current.liveView = viewController.view
PlaygroundPage.current.needsIndefiniteExecution = true
let button = UIButton(primaryAction: UIAction { _ in showModal() })
button.setTitle("Show page sheet", for: .normal)
viewController.view.addSubview(button)
button.frame = CGRect(x: 90, y: 100, width: 200, height: 44)
func showModal {
let viewControllerToPresent = UIViewController()
viewControllerToPresent.view.backgroundColor = .blue.withAlphaComponent(0.5)
viewControllerToPresent.modalPresentationStyle = .pageSheet // or .formSheet
if let sheet = viewControllerToPresent.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.prefersGrabberVisible = true
}
viewController.present(viewControllerToPresent, animated: true, completion: nil)
}
The feature you are asking is not available in UIKit.
You have to implement custom view-controller transition animation with subclassing UIPresentationController for rendering that pull up/down handle.
UIPresentationController (developer.apple.com)
For custom presentations, you can provide your own presentation controller to give the presented view controller a custom appearance. Presentation controllers manage any custom chrome that is separate from the view controller and its contents. For example, a dimming view placed behind the view controller’s view would be managed by a presentation controller. Apple Documentation
This can be achieved by any UIView or you can use any image if you want by adding subview to UIPresentationController's contentView above the presentedView.
To provide the swipe gesture to dismiss/present, you must implement UIPercentDrivenInteractionController.
You can refer to this tutorial below for detailed understanding.
UIPresentationController Tutorial By raywenderlich.com
You should look for presentationDirection = .bottom in your case.
For gesture driven dismissal, you should check below tutorial
Custom-UIViewcontroller-Transitions-getting-started
I hope this might help you.
If you need to add this indicator within the view controller that is being presented if you do not want to do any custom presentations and just work with the default transitions.
The first thing to think about is your view hierarchy, is the indicator going to be part of your navigation bar or perhaps your view does not have navigation bar - so accordingly you probably need some code to find the correct view to add this indicator to.
In my scenario, I needed a navigation bar so my view controllers were within a navigation controller but you could do the same inside your view controllers directly:
1: Subclass a Navigation Controller
This is optional but it would be nice to abstract away all of this customization into the navigation controller.
I do a check to see if the NavigationController is being presented. This might not be the best way to check but since this is not part of the question, refer to these answers to check if a view controller was presented modally or not
class CustomNavigationController: UINavigationController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// this checks if the ViewController is being presented
if presentingViewController != nil {
addModalIndicator()
}
}
private func addModalIndicator() {
let indicator = UIView()
indicator.backgroundColor = .tertiaryLabel
let indicatorSize = CGSize(width: 30, height: 5)
let indicatorX = (navigationBar.frame.width - indicatorSize.width) / CGFloat(2)
indicator.frame = CGRect(origin: CGPoint(x: indicatorX, y: 8), size: indicatorSize)
indicator.layer.cornerRadius = indicatorSize.height / CGFloat(2.0)
navigationBar.addSubview(indicator)
}
}
2: Present the Custom Navigation Controller
let someVC = UIViewController()
let customNavigationController = CustomNavigationController()
customNavigationController.setViewControllers([stationsVC], animated: false)
present(playerNavigationController, animated: true) { }
3: This will produce the following results
You might need to alter some logic here based on your scenario / view controller hierarchy but hopefully this gives you some ideas.

custom navigation bar with image and two titles in iOS

I am trying to achieve the following navigation bar with two titles and an image:
Large title variant:
Small title variant:
I tried subclassing UINavigationBar and adding subviews to it, but they did not render at all.
I tried setting a titleView in storyboard, however it seemed like the titleView is constrained in its height.
What is the proper way to achieve this custom navigation bar?
I also tried this (and setting the viewController in Storyboard to that class):
class NavViewController: UINavigationController {
var titleView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationBar.topItem?.titleView?.backgroundColor = .gray
titleView.frame = CGRect(x: 0, y: 0, width: 100, height: 300)
self.navigationBar.topItem?.titleView = titleView
}
}
Inside the ViewControllerin viewDidLoad, add self.navigationController?.navigationBar.addSubview(imageView). (no need for subclassing)
There is even support for AutoLayout inside UINavigationbar, which is great for animation.
Design your custom view seprately in a xib file, then set that xib as the titleview for your navigationbar
self.navBar.topItem?.titleView = logoImage
Do this for large title, for the smaller one only populate an image in the titleView.

Adding view beneath navigation bar in navigation controller

I'm trying to add a UIView beneath the UINavigationBar in my UINavigationController.
The view will serve as a placeholder for information messages (for example if we are having issues and content is not getting updated).
Adding the view it self and setting it's constraints is not an issue, but it is overlapping the content of the views that is contained in the navigation controller, which is not want I want. How can I set the content of the contained viewcontroller to respect the space which this new view takes up?
The screenshot is showing my custom (orange) view overlapping the content of the viewController that was pushed on to the navigation controller.
Try Subclassing the UINavigationController and then add your orange view's height constraint to it. and call the function whenever you need it
import UIKit
class CustomNavigationController: UINavigationController{
#IBOutlet weak var topViewHeight: NSLayoutConstraint!
func animateHeight(height: CGFloat){
UIView.animate(withDuration: 0.2) {
self.viewControllers.forEach{ vc in
let v = vc.view.frame
vc.view?.frame = CGRect(x: 0, y: height, width: v.width, height: v.height)
}
self.topViewHeight.constant = height
}
}
}
how to use it?
in your VC where you want to show/hide it:
(self.navigationController as? CustomNavigationController)?.animateHeight(height: 50)

Subviews in presented ViewController gets wrong position after rotation

I'm experimenting with UISearchController, but I can't get it right. I present a clean UISearchController from my own UIViewController, and it looks great - but as soon as I rotate the device, it gets shifted a few points up or down.
To recreate this, just do these few steps:
Create a new Single View project
Delete Main.storyboard from the project files and remove its name from the project settings (Project -> General -> Target -> Main Interface)
In AppDelegate.swift:
application didFinishLaunchingWithOptions...{
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
ViewController.swift:
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let button = UIButton(frame: CGRect(x: 20, y: 200, width: 100, height: 40))
button.setTitle("Search", for: .normal)
button.backgroundColor = .black
button.addTarget(self, action: #selector(click), for: .touchUpInside)
self.view.addSubview(button)
}
#objc func click(){
self.present(UISearchController(searchResultsController: nil), animated: true, completion: nil)
}
}
And that's it. This is what I'm seeing:
Presenting the search when the device is in portrait mode looks great in portrait - but if you rotate the device to landscape while presenting the searchbar, it will be wrongly positioned, a few pixels above the top of the screen.
Presenting the search when in landscape will yield the opposite. It looks great in landscape, but when rotating it to portrait the entire search controller view will be pushed down a few pixels.
It's not a matter of height size on the bar. The entire bar gets pushed up/down.
I tried investigating a bit further. I presented the search controller from landscape mode and rotated to portrait, and then debugged the view hierarchy:
To be honest, I'm not quite sure what I'm looking at. The top-most view is a UISearchBarBackground embedded within a _UISearchBarContainerView, which is within a _UISearchControllerView.
As you can see in the size inspector on the right side, the middle-view "container" has y: 5, which makes no sense. When debugging the correct state, y is 0. What's really interesting is that the top-most view has y: -44 in this corrupt situation, and it has the same when it's correct - but you can clearly see that there is some space leftover above it. There seem to be three different y-positions. I don't get it.
I've read some guides on how to implement a UISearchController, but literally every single example I find is people modally presenting a custom ViewController that contains a SearchController. This will result in the entire custom ViewController being animated up from below.
Since the UISearchController is a subclass of UIViewController, I wanted to test out presenting it directly, not as part of a regular UIViewController. This gives the cool effect that the searchBar animates in from above, and the keyboard from below.
But why doesn't this work?
Edit: I just found out that if I enable Hide status bar in the project settings, the UISearchController looks even more correct in landscape than the "correct state" from above, and even animates correctly to portrait. It's super weird, because the status bar doesn't change at all. It was never visible in landscape. Why does this happen? It seems so buggy. Look at these three states:
The first state is when showing search controller from portrait then rotating to landscape (doesn't matter if Hide status bar is enabled or not.
The second state is when showing search controller from landscape if Hide status bar is false
The third state is when showing search controller from landscape if Hide status bar is true.
As stated in the documentation:
Although a UISearchController object is a view controller, you should
never present it directly from your interface. If you want to present
the search results interface explicitly, wrap your search controller
in a UISearchContainerViewController object and present that object
instead.
Try to wrap your UISearchController inside a UISearchContainerViewController.
How about make a custom view contains search bar.
Make it on top of the layers.
And when rotate occurs update frame/constraints of it.
This is covered in guidelines for designing for iPhone X. Now Navigation bar contains search controller. You can instanciate the search controller and set it to navigationItem.searchController. Now it will handle the search controller while rotation.
var search = UISearchController(searchResultsController: nil)
self.navigationItem.searchController = search
You can do this tric
class ViewController: UIViewController {
let sc = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let button = UIButton(frame: CGRect(x: 20, y: 200, width: 100, height: 40))
button.setTitle("Search", for: .normal)
button.backgroundColor = .black
button.addTarget(self, action: #selector(click), for: .touchUpInside)
self.view.addSubview(button)
}
#objc func click(){
self.present(sc, animated: true, completion: nil)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
var mFrame = sc.view.frame
mFrame.origin.y = 5.0
sc.view.frame = mFrame
}

NavigationItem's titleView not animating correctly

I have looked at all the similar questions I could find and I have not found a solution to this problem.
I am setting the titleView property like this in viewDidLoad:
self.navigationItem.titleView = label
The view controller is part of a navigation stack. When you tap on a row in in the previous view controller it pushes this controller onto the stack. Completely normal UINavigationController stuff.
As this view controller begins to animate in, the label appears at the origin (top left) and then stays there until the view controller finishes animating and then it jumps (no animation) to the proper position in the middle of the nav bar.
After tapping on the back button the title view animates out correctly, just like a normal title would.
Here is the code in viewDidLoad:
let label = UILabel(frame: CGRect.zero)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .red
label.text = "test"
label.sizeToFit()
self.navigationItem.titleView = label
Things I've tried:
Specify a frame: no change. Plus I don't want to specify a frame. I don't want to make assumptions about the height of the nav bar.
Moved it to viewWillAppear: no change.
Moved it to viewDidAppear: better but still not right. The label does not appear at all until the animation is complete and then it appears where it should, no animation it just appears. The correct behaviour is to animate in from right to left like a normal title would.
This is easily reproducible with the Master-Detail project template in Xcode. If you want to try it just add the code above to the top of configureView() in DetailViewController.swift. In that template the navigation item's title is hardcoded into the storyboard. You can easily remove it by searching for "Detail". Click the result that says Navigation Item: Title = "Detail" and then remove the string from the Inspector pane.
Update
#synndicate's suggestion does work perfectly for the UILabel example above. But my real problem is with a UIStackView. When I use a stack view following the approach #synndicate suggests I get yet another animation problem. This time the title view starts sliding in but animates all the way to the origin. When the animation is finished it snaps to where it should be.
Here's the code in prepare(for:sender:) as #synndicate suggests...
let label = UILabel(frame: CGRect.zero)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .red
label.text = "test"
let stackView = UIStackView(frame: CGRect.zero)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(label)
stackView.sizeToFit()
controller.navigationItem.titleView = stackView
Any further suggestions?
Also, this is Xcode 8 and iOS 10.
Update 2
I have discovered that the stack view code above animates perfectly (just like an ordinary title would) in viewDidLoad for a view controller where the root of the nav hierarchy is a UINavigationController. The problem occurs when the root of the nav hierarchy is a UISplitViewController.
So I guess I could update my question to this...
How do I configure a UIStackView that will be set on the navigationItem's titleView property where the root of the navigation hierarchy is a UISplitViewController?
Are you able to move the code to before you push the view controller on the stack?
e.g. In MasterViewController.swift of the Master-Detail project
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let object = objects[indexPath.row] as! NSDate
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
let label = UILabel()
label.textAlignment = .center
label.backgroundColor = .red
label.text = "test"
label.sizeToFit()
controller.navigationItem.titleView = label
}
}
}
If you want the stackView to appear on a specific view controller of the navigation stack, simply add it in the view controller
self.view.addSubview(stackView)

Resources