iOS: RefreshControl + large title doesn't work well in UITabBarController - ios

I have UIViewController with NavigationBar that is a part of UITabController.
Inside UIViewController I have only UITableView.
NavigationBar is transparent and blurred.
TableView top constraint is to superview, so when I scroll content it goes behind navigation bar.
I have large titles enabled:
Problem:
loadData data triggered immediately when I start to scroll down.
Right after I scroll few pixels down.
All works fine if I remove largeTitles,
but with large titles it feels like refreshControl already at position when it ready to trigger .valueChanged
Also it works fine if to remove tab bar and load navigationController directly as root. But I need tabbar.
Full code:
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
window.makeKeyAndVisible()
self.window = window
window.rootViewController = TabBarController()
return true
}
}
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let navigationController = UINavigationController(rootViewController: ViewController())
navigationController.navigationBar.prefersLargeTitles = true
viewControllers = [navigationController]
}
}
class ViewController: UIViewController {
private let tableView = UITableView()
private let refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Test"
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
tableView.delegate = self
tableView.dataSource = self
tableView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(loadData), for: .valueChanged)
}
#objc func loadData() {
print("loadData triggered")
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.refreshControl.endRefreshing()
}
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell
if let defaultCell = tableView.dequeueReusableCell(withIdentifier: "defaultCell") {
cell = defaultCell
} else {
cell = UITableViewCell(style: .value1, reuseIdentifier: "defaultCell")
}
cell.textLabel?.text = "Test"
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
10
}
}
BUT
All works fine if it's from StoryBoard:

Related

UISearchController in iOS 9 - TextField from UISearchBar clipping out of navigation item

I am trying to get a smooth Searchbar on navigation item on iOS 9, which means I can't use navigationItem.searchController property since its only iOS 11 only.
class SearchContainerViewController: UITableViewController {
let dataSource = ["1", "2", "3", "4", "5"]
override public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
override public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = dataSource[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dismiss(animated: true, completion: nil)
}
}
class SearchViewController: UISearchController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
class MyViewController : UIViewController, UISearchResultsUpdating, UISearchBarDelegate {
lazy var searchButton = UIBarButtonItem(title: "Search", style: UIBarButtonItem.Style.plain, target: self, action: #selector(showSearchBar))
var searchViewController: SearchViewController = {
let container = SearchContainerViewController()
let searchController = SearchViewController(searchResultsController: container)
return searchController
}()
override func viewDidLoad() {
super.viewDidLoad()
setupSearchController()
setupSearchButton()
}
func setupSearchController() {
searchViewController.searchResultsUpdater = self
searchViewController.searchBar.delegate = self
searchViewController.dimsBackgroundDuringPresentation = false
searchViewController.hidesNavigationBarDuringPresentation = false
searchViewController.searchBar.searchBarStyle = .minimal
searchViewController.searchBar.showsCancelButton = true
definesPresentationContext = true
}
#objc func showSearchBar() {
UIView.animate(withDuration: 0.75) {
self.navigationItem.titleView = self.searchViewController.searchBar
self.navigationItem.rightBarButtonItem = nil
self.searchViewController.searchBar.becomeFirstResponder()
}
}
func setupSearchButton() {
UIView.animate(withDuration: 0.75) {
self.navigationItem.titleView = nil
self.navigationItem.rightBarButtonItem = self.searchButton
}
}
// MARK: Conforms to UISearchResultUpdating
public func updateSearchResults(for searchController: UISearchController) { }
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
setupSearchButton()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.layoutSubviews()
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let newWindow = UIWindow(frame: UIScreen.main.bounds)
let mainViewController = MyViewController()
let navigationController = UINavigationController(rootViewController: mainViewController)
newWindow.backgroundColor = .white
newWindow.rootViewController = navigationController
newWindow.makeKeyAndVisible()
window = newWindow
return true
}
}
Though the result is kinda disappointing since the textview with StatusBar is clipping out of the navigation item context, there is anything im doing wrong and could've done better?
Appreciate your support.
In before people down voting the question for no reasons, I am going to do something different and answer my own question.
The clipping happened because in both situations the height of the navigationItem were different because of they are somewhat stretchable if put big content in titleView (like a searchBar).
I've set the searchBar from the start on the navigationItem and just toogle their isHidden property when it should be done.
#objc private func activateSearch() {
UIView.animate(withDuration: 0.75) {
self.navigationItem.titleView?.isHidden = false
self.navigationItem.rightBarButtonItem = nil
self.searchController.isActive = true
}
}
private func deactivateSearch() {
UIView.animate(withDuration: 0.75) {
self.navigationItem.titleView?.isHidden = true
self.navigationItem.rightBarButtonItem = self.searchButton
self.searchController.isActive = false
}
}

How to fix iOS10 safe area and topLayoutGuide issue?

I have a question about safe area.
And I write a code and build in two simulators of iOS version 10.2 and 11.4.
It shows different part of red rectangle area like following image.
What's wrong with me?
Thanks.
code here:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UINavigationController(rootViewController: ViewController())
window?.makeKeyAndVisible()
return true
}
}
class ViewController: UIViewController {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "TITLE HERE"
tableView.dataSource = self
tableView.delegate = self
self.view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
make.left.equalTo(10)
make.top.equalTo(self.topLayoutGuide.snp.bottom)
make.right.bottom.equalTo(-10)
}
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "\(indexPath) TEST"
return cell
}
}

SearchResultsController obscures screen when used with UINavigationItem

I'm using new integrated UISearchBar available in iOS 11, the problem is as soon as I tap on the searchbar and start typing text, the SearchResultsController obscures the whole screen, including the searchbar. It is impossible to dismiss the results controller or cancel search afterwards.
To demonstrate the issue, I've configured a minimal reproducible example:
import UIKit
let reuseid = "reuseIdentifier"
class TableViewController: UITableViewController, UISearchResultsUpdating {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseid)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseid, for: indexPath)
return cell
}
func updateSearchResults(for searchController: UISearchController) {
print(searchController.searchBar.text)
}
}
class ViewController: UIViewController {
var searchController: UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
searchController = UISearchController(searchResultsController: TableViewController())
navigationItem.searchController = searchController
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.backgroundColor = UIColor.white
window?.makeKeyAndVisible()
let nc = UINavigationController(rootViewController: ViewController())
window?.rootViewController = nc
return true
}
}
Initial state
Tapping on the SearchBar
The screen is obscured by the UITableView - colored in green
What could cause this bug and how can it be avoided?
Solved by adding this to the SearchResultsUpdater:
edgesForExtendedLayout = []
With default edges, the ResultsController tries to extend its view beyond the UINavigationBar and hence, obscures it:

How to hide searchBar under navigation bar and show it when user pull the tableView down?

in my app I want to implement searchBar above the tableView which will hide under navigationBar. when user pull the tableView down then I want to show the searchBar. Please tell me how I can achieve this in swift. Thanks!!
enter image description here
Set up rootViewController with UINavigationController in AppDelegate.swift.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = UINavigationController(rootViewController: ViewController())
return true
}
Then in ViewController.swift, add TableView by properly implementing UITableViewDataSource and UITableViewDelegate methods.
Then, declare UISearchController and add it to tableHeaderView instead of adding as subview. Then, set the content offset of tableView so that by default the searchController is hidden. When tableView is scrolled down, then only searchBar is shown.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private var myData: NSArray = ["One", "Two", "Three"]
private var tableView: UITableView!
//lazy var searchBar:UISearchBar = UISearchBar()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
navigationItem.title = "Home"
tableView = UITableView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
tableView.dataSource = self
tableView.delegate = self
self.tableView.backgroundColor = .brown
let searchBarController: UISearchController = UISearchController(searchResultsController: nil)
tableView.tableHeaderView = searchBarController.searchBar
tableView.setContentOffset(CGPoint.init(x: 0, y: 44), animated: false)
self.view.addSubview(tableView)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("\(indexPath.row)")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
cell.textLabel?.text = myData[indexPath.row] as! String
cell.backgroundColor = .brown
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Initially when the application is first launched searchBar is hidden, only tableView is displayed:
After tableView is scrolled down, then searchBar appears:
Simply make the table view or collection view scroll to the first row right after setting the search controller.
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.searchController = searchController
tableView.scrollToRow(
at: .init(row: 0, section: 0),
at: [.bottom, .left],
animated: false
)
}
I could hide the searchBar with this code
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
self.searchbar()
self.automaticallyAdjustsScrollViewInsets = false
tableView.contentInset = UIEdgeInsets.zero
tableView.tableHeaderView = searchController.searchBar
//Hide SearchBar
tableView.setContentOffset(CGPoint.init(x: 0, y: 44), animated: false)
}
func searchbar() {
searchController.searchResultsUpdater = self as UISearchResultsUpdating
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
}
To initially hide the searchBar, simply set the navigationItem.searchController property after your table view (or collection view) has been populated with data.

iOS use Swift files as first view

In Xcode 6, is it possible to use a .swift file as the 1st view the user will see when they open my app?
The file below is for a table view as I prefer to use swift instead of IB (too many views makes IB look messy).
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView!
let items = ["Hello 1", "Hello 2", "Hello 3"]
override func viewDidLoad() {
super.viewDidLoad()
self.view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
self.tableView = UITableView(frame:self.view.frame)
self.tableView!.dataSource = self
self.tableView!.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(self.tableView)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel.text = "\(self.items[indexPath.row])"
return cell
}
}
var ctrl = ViewController()
According to Apple's View Controller Programming Guide, "When working with view controllers directly, you must write code that instantiates the view controller, configures it, and displays it." However it also states "You gain none of the benefits of storyboards, meaning you have to implement additional code to configure and display the new view controller." That being said if you still desire to do this, this is how you do it (in your app delegate):
var window: UIWindow?
var viewController: ViewController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.backgroundColor = UIColor.whiteColor()
viewController = ViewController()
// Any additional setup
window?.rootViewController = viewController
window?.makeKeyAndVisible()
return true
}

Resources