How to initally hide searchbar in Navigation controller on iOS 13? - uikit

In iOS 13 the behavior has changed so that by default when Navigation controller appears the search bar is visible (when UISearchController is assigned to a navigationItem.searchController). Some system apps appear with the search bar hidden (you need to swipe down for it to appear), but I don't see any specific property that would allow this. How to achieve this - maybe there is some property or some method to do that?

Via experimentation, I have discovered that if you delay assigning the search controller to the navigation item until viewWillLayoutSubviews or viewDidLayoutSubviews, the search controller starts out hidden, as desired. However, this if you do this on iOS 12 or earlier, the search controller will not be revealed when scrolling down.
I ended up doing the following with a messy version check, which is working for me:
override func viewDidLoad() {
super.viewDidLoad()
searchController = /* make search controller... */
if #available(iOS 13, *) {
// Attaching the search controller at this time on iOS 13 results in the
// search bar being initially visible, so assign it later
}
else {
navigationItem.searchController = searchController
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
navigationItem.searchController = searchController
}

To start with a hidden searchBar, simply set the navigationItem.searchController property after your table view (or collection view) has been populated with data.

Inspired by bunnyhero's answer I put the code responsible for setting the UISearchController in navigationItem inside the viewDidAppear method. Seems to be working every time for me on iOS 14/15
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if navigationItem.searchController == nil {
navigationItem.searchController = searchController
}
}
Edit: I was overly optimistic. On iOS 15.2 this method stopped working for me. What I did to fix it was to move the code after reloading my table/collection view.

I find this works:
self.searchController.searchBar.hidden = YES;
You will need to unhide at the appropriate time.

I managed to make this work by setting isTransculent false on the navigationBar and having initial data on UITableView or UICollectionView. If you have 0 cells initially and trigger reloadData after some time (maybe a network call), SearchBar is visible initially. So have a dummy cell or something similar initially and load the data later, if that's the case for you.
navigationController?.navigationBar.isTranslucent = false

One should set searchController after tableView gets frame
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
super.scrollViewDidScroll(scrollView)
if !scrollView.frame.isEmpty, navigationItem.searchController == nil {
navigationItem.searchController = searchController
}
}

This is what works for me. I have a UISegmentedControl that reloads the tableView when filter changes.
With FRC:
guard let count = try? fetchedResultsController.managedObjectContext.count(for: request) else { return }
called after tableView.reloadData()
navigationItem.searchController = count > 20 ? searchController : nil

Swift 5.2 & iOS 13.3.1:-
Try like this. It works fine
navigationItem.hidesSearchBarWhenScrolling = false

Related

NSCollectionView inside NSTabViewController

I am facing a strange behavior of NSCollectionView inside a NSTabViewController.
I think that this issue appeared when I upgraded to High Sierra (not sure though).
My app has four tabs and each of them contains a collection view:
When I launch the app, the first tab is fine, problem is when I switch to other tabs. The collection view is not properly laid out:
As soon as I touch the window border the collection view reorganizes normally.
I tried to force the collection view to layout (.collectionView.layout()) without success.
Can anyone help me?
Thank you
This helped me out:
Subclassed the NSTabViewController
Added this:
override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
let controller = tabViewItem?.viewController as? MyController
controller?.collectionView.frame = (controller?.view.frame)!
}
I ran into the same problem recently and the provided fix didn't work with my particular subview layout, but this did:
override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
if let controller = tabViewItem?.viewController as? MyController,
let collectionView = controller.collectionView,
let contentSize = collectionView.collectionViewLayout?.collectionViewContentSize,
contentSize.height > collectionView.frame.size.height
{
collectionView.frame.size.height = contentSize.height
}
}
Maybe this is a more generally applicable solution.

Dismissing keyboard in UITextField with RAC(5)?

Newbie to ReactiveCocoa and ReactiveSwfit here... Sorry if the answer is obvious.
I am trying to adapt the Start Developing iOS Apps with Swift sample to ReactiveSwift / ReactiveCocoa, and I am running into an issue with "translating" the UITextField's Delegate method -- which gets rid of the keyboard and essentially ends the editing (so I can capture the text field in the mealNameLabel) :
func textFieldShouldReturn(_ textField: UITextField) -> Bool
I am using
nameTextField.reactive.textValues.observeValues { value in
viewModel.mealName.swap(value ?? "")
}
// Setup bindings to update the view's meal label
// based on data from the View Model
mealNameLabel.reactive.text <~ viewModel.mealLabel
to get the value from the text field into the view model and percolate the view model's label back to the UILabel (convoluted...)
That works fine, as long as I maintain the viewController as the UITextField's delegate and I still implement the method depicted in the tutorial and mentioned above. Essentially :
override func viewDidLoad() {
super.viewDidLoad()
nameTextField.delegate = self
// view controller logic
...
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// Hide the keyboard.
textField.resignFirstResponder()
return true
}
I tried using
nameTextField.reactive.controlEvents
but that failed miserably due to my lack of understanding of controlEvents (docs anywhere ?).
So what do I need to do to make the keyboard disappear when the user is done editing, the "reactive way" ?
Thanks !!!
(Of course right after I post my question...)
It looks like this might actually do the trick :
nameTextField.reactive.controlEvents(UIControlEvents.primaryActionTriggered)
.observeValues { textField in
textField.resignFirstResponder()
}
After fiddling with the different event types, it looks like .primaryActionTriggered is what gets triggered when the "Done" button is pressed.
Any better way to do this ?

Simple displaying of second window: OS X & Swift

I'm trying to find how to bring up a second view/window after pushing a button on my primary window. I have read about segues and I can get the first window to display the second but the second is not connected to a view controller so I can't add any code to any controls on the second view. Try as I might I cannot create a SecondViewController.swift file and connect it to a window controller or a view controller. The tutorials I have found all deal with iOS and I want OS X which means there are just enough differences to keep me from figuring this out.
Can anyone show me how to do this?
Ta,
A.
First make new file like:
After that, put these codes in your classes and that should do it.
class SecondWindowController: NSWindowController {
convenience init() {
self.init(windowNibName: "SecondWindowController")
}
}
class ViewController: NSViewController {
private var secondWindowController: SecondWindowController?
#IBAction func showSecondWindow(sender: AnyObject) {
if secondWindowController == nil {
secondWindowController = SecondWindowController()
}
secondWindowController?.showWindow(self)
}
}

Xcode XIB: how to implement a ScrollView or PageControl to swipe sub-views?

I'm building a typical Xcode 6 iOS app.
My goal is:
A screen that has an sub-area that can be swiped to change the content.
For example, the home screen has a logo image, a middle area that I want to be swipeable, and a bottom button.
When the user swipes (or taps) the middle area, the area shows the next (or previous) information, which is a typical UIImage and UILabel caption.
The rest of the screen stays the same, i.e. there is no navigation change.
The code is here. It use the recommendations from the StackOverflow post here.
My question: how can I implement the code below better, while still using an XIB?
My current implementation does work, and uses this approach...
A typical Swift Demo.swift file that is a UIViewController that has:
the page index, min, and max
outlets for the PageControl, UIImageView, and UILabel
actions for the page control change, and the image swipe or tap
A typical Demo.xib file that has:
a typical UIViewController for the entire screen
a UIImageView and UILabel for the changeable image and caption text
a PageControl to indicate what tutorial page the user is viewing
I am seeking better ways to accomplish this; I've read many of Xcode tutorials and so far none seem definitive for Xcode 6, XIBs, and Swift.
Here are some implementations that I've researched that seem promising...
Is there a way to implement a subview area in the XIB?
For example, can Xocde show the XIB with a rectangular area that is intended for the changeable content?
Is there an idiomatic way to write the code for changeable content?
For example, by using a ScrollView, perhaps that contains a UIPageViewController?
Is there a way to make a PageControl XIB object large enough to cover the entire UIImageView and UILabel, so I can skip making the UIImageView respond to gestures.
In my Xcode, the PageControl seems to have an uneditable height that is always 37.
The bounty will be for expert advice.
To make a UIPageViewController swipe-able you should implement the UIPageViewControllerDataSource protocol and provide a view controller for the pageViewController(pageViewController:viewControllerBeforeViewController) -> UIViewController? and the ...viewControllerAfterViewController) methods.
Provide a custom view controller for each page that presents an image and label and takes them as properties so you can provide them from the PageViewController.
My trick it to create a method that instantiates a new view controller in these methods:
// MARK:- UIPageViewControllerDataSource
extension MyPageViewController: UIPageViewControllerDataSource {
func viewControllerWithIndex(var index: Int) -> UIViewController! {
let viewController = storyboard?.instantiateViewControllerWithIdentifier("MyViewController") as! MyViewController // This VC has to be in the storyboard, otherwise just use MyVC()
// Adjust the index to be cyclical, not required
if let count = data?.endIndex {
if count == 1 && index != 0 { return nil }
if index < 0 { index += count }
index %= count
}
viewController.view.tag = index
viewController.record = data?[index]
return viewController
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let index = viewController.view?.tag ?? 0
return viewControllerWithIndex(index + 1)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let index = viewController.view?.tag ?? 0
return viewControllerWithIndex(index - 1)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return countAndSetupPageControl()
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return viewController?.view.tag ?? 0
}
}
Now for the "sub-area" you will need to implement a ChildViewController. If you're using storyboards you can just drag a Container View and put PageViewController in the embedded view controller, otherwise you need to add the PageViewController.view as a subview and set the frame to the middle.
You can find more info in the apple documentation but basically you MUST call these methods:
addChildViewController(pageViewController)
view.addSubView(pageViewController.view)
pageViewController.view.frame = ... // This is your "sub-area"
pageViewController.didMoveToParentViewController(self)
If you add a height constraint to PageControl you can set it's height to whatever you want.
I don't see a problem with your current implementation. Changing it to use a PageViewController would be quite more work.
If I were you I would add an animation in pageUpdate function so the image would fade in or slide in...
It would only make sense to use a PageViewController if you want to be able to scroll to the next page (as in content moving in the same time your finger is moving onscreen). And you can use a PageViewController or a CollectionView with paging enabled.

Use NSToolBar Outlet xcode 6 and Storyboard?

I am trying to add an outlet into my viewcontroller for a toolbar item in my window controller. I have tried playing around with first responder and bindings but have not been able to find any solutions.
A similar question that was answered provided some insight but no one has mentioned anything about IBOutlets other than still asking how to add them in the comments. The answer has been accepted so i am assuming no one will add to it.
How to use NSToolBar in Xcode 6 and Storyboard?
Incase my question is unclear at all, i would like to be able to add this to my storyboard program
#IBOutlet weak var Mytoolbar: NSToolbarItem!
func enabletoolbar()
{
Mytoolbar.action = "FunctionIn.ViewController.swift"
Mytoolbar.enabled = true
}
I found a decent workaround by adding IBOutlets to my custom NSWindow class and using the storyboard to connect my views to the IBOutlets. Then, I accessed these views from my NSViewController class by getting them from the custom NSWindow.
Basically you need to set the action and other properties to the toolbaritem but not in the toolbar. So try the same.
i ended up doing this in my view controller which seems to work
override func viewDidLayout() {
var x = self.view.window?.toolbar?.items[1].label
println(x)
if(self.view.window?.toolbar?.items[0].label! != "Check")
{
toobarediting()
}
println("didlay")
}
func toobarediting() {
self.view.window?.toolbar?.insertItemWithItemIdentifier("Check", atIndex: 0)
}
func toolbarcheck(functiontoset: Selector) {
var y = self.view.window?.toolbar?.items[0] as NSToolbarItem
y.action = functiontoset
if(functiontoset != nil)
{
y.enabled = true
}
}
It seems to allow me to make the tool bar button clickable/unclickable when ever i require it to change it just seems so much more bulky and error prone than
myitem.enable = fale
myitem.action = nil
is this really the best way for a storyboard based application in osx?
While connectiong IBActions works by using either the First Responder or by adding an "Object" to the scene, then changing its class to the window's view controller class, this doesn't help with IBOutlets and delegates that you'd like to point to the view controller.
Here's a work-around for that:
Add the Toolbar to the View Controller, not to its Window. That way, you can make all the IBOutlet connections in the View Controller Scene easily. I've done that for years and found no issues with it, even when using Tabs.
You'll have to assign the window's toolbar in code, then. E.g. like this:
#interface ViewController ()
#property (weak) IBOutlet NSToolbar *toolbar; // connect this in your storyboard to the Toolbar that you moved to the View Controller Scene
#end
- (void)viewWillAppear {
[super viewWillAppear];
self.view.window.toolbar = self.toolbar;
}

Resources