Continuous scrolling between tableView and scrollView - ios

I have a parent scroll view with a uiview header and table view. I am trying to have a similar behavior to Twitter or Instagram profile page. You start scrolling the parent scroll view and as soon as the header is gone there is a continuous transition from the scroll view scrolling to the table view scrolling without having to lift my finger and replace it on the table view. Right now the current behavior is a little jank. I have to lift my finger and put it on the table view after the scroll view content offset is past the header to start swiping the table view.
MainViewController
import UIKit
import XLPagerTabStrip
class MainViewController: UIViewController {
lazy var headerViewController: UIViewController = {
let header = UIViewController()
return header
}()
lazy var bottomViewControllers: BottomPageViewController = {
let bvc = BottomPageViewController()
return bvc
}()
lazy var scrollView: UIScrollView = {
let sv = UIScrollView()
sv.delegate = self
sv.showsVerticalScrollIndicator = false
sv.bounces = true
sv.bounces = false
return sv
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(scrollView)
let f = UIScreen.main.bounds
scrollView.frame = CGRect(x: f.minX, y: f.minY, width: f.width, height: f.height)
add(headerViewController, to: scrollView, frame: CGRect(x: 0, y: 0, width: f.width, height: 150))
add(bottomViewControllers, to: scrollView, frame: CGRect(x: 0, y: headerViewController.view.bounds.height, width: f.width, height: f.height))
scrollView.contentSize = CGSize(width: f.width, height: f.height * 2)
if let vcs = bottomViewControllers.viewControllers as? [BottomViewController] {
for vc in vcs {
vc.delegate = self
}
}
}
}
extension MainViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let controllers = bottomViewControllers.viewControllers as? [BottomViewController] else { return }
let selectedController = controllers[bottomViewControllers.currentIndex]
if self.scrollView == scrollView {
selectedController.tableView.isScrollEnabled = self.scrollView.contentOffset.y >= 150
print(scrollView.contentOffset.y)
scrollView.isScrollEnabled = !selectedController.tableView.isScrollEnabled
bottomViewControllers.view.frame.origin.y = max(150, scrollView.contentOffset.y)
}
}
}
extension MainViewController: CustomScrollDelegate {
func tableViewScroll(_ viewController: BottomViewController) {
print(viewController.tableView.contentOffset.y)
viewController.tableView.isScrollEnabled = viewController.tableView.contentOffset.y > 0
}
}
BottomViewController
import UIKit
import XLPagerTabStrip
protocol CustomScrollDelegate {
func tableViewScroll(_ viewController: BottomViewController)
}
class BottomViewController: UITableViewController {
var pageTitle: String?
var pageIndex: Int = 0
var delegate: CustomScrollDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .red
self.tableView.isUserInteractionEnabled = false
self.tableView.bounces = true
self.tableView.showsVerticalScrollIndicator = true
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "TETST")
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TETST", for: indexPath) as? UITableViewCell else { return UITableViewCell() }
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1000
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
delegate?.tableViewScroll(self)
}
init(pageTitle: String, pageIndex: Int) {
self.pageTitle = pageTitle
self.pageIndex = pageIndex
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension BottomViewController: IndicatorInfoProvider {
func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
return IndicatorInfo.init(title: pageTitle ?? "Tab \(pageIndex)")
}
}

You should simply add your header as a header to the UITableView (not a section header!) and let it scroll with the rest of the UITableView: How to scroll the header along with the UITableView?
Also you can make the UITableView link it's contentSize directly to it's height so the UITableView itself doesn't scroll at all, ever, like this: Resizing UITableView to fit content
It all kind of depends if you add rows dynamically by paging etcetera how complex your solution would be. But I think you would get there most of the time by simply setting up the UITableView header in the correct mode

Related

UITableview to cell detail with image

I currently have a UITabelview that displays a list of medications, each connecting to its own scene with its own image view.
Is it possible to connect the UITableview to only one scene and just change the image based on what the user picks within the table?
This is the link to the image to view my storyboards:
https://ibb.co/6FcDySW
Code is also displayed.
class MedicationsController: UITableViewController {
var RXnames = [String] ()
var RXidentities = [String] ()
var RXdetail = [String] ()
#IBAction func home(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
RXnames = ["Acetaminophen", "Activated Charcoal","Adenosine","Albuterol", "Amiodarone", "Aspirin","Atropine Sulfate",
"Calcium Chloride","Dextrose","Diltiazem","Diphenhydramine","Dopamine","Epinephrine","Etomidate","Fentanyl","Furosemide",
"Glucagon","Glucose (Oral)","Ibuprofen","Ipratropium Bromide","Ketamine Hydrochloride","Ketoralac","Lidocaine","Lorazepam",
"Magnesium Sulfate","Methylprednisolone","Metoprolol","Midazolam","Morphine Sulfate","Naloxone","Nitroglycerin",
"Nitrous Oxide","Norepinephrine","Ondansetron","Oxygen","Promethazine","Racemic Epinephrine","Rocuronium","Sodium Bicarbonate",
"Succinylcholine","Transexamic Acid","Vecuronium"]
RXidentities = ["1","2","3","4","5","6","7","8","9","10",
"11","12","13","14","15","16","17","18","19","20",
"21","22","23","24","25","26","27","28","29","30",
"31","32","33","34","35","36","37","38","39","40","41","42","43"]
RXdetail = ["Tylenol", "CharcoAid","Adenocard","Ventolin", "Cordarone", "Bayer","Atropen",
"CaCl","D50W","Cardizem","Benadryl","Intropin","Adrenalin","Amidate","Sublimaze","Lasix",
"Glucagon","Glucose (Oral)","Motrin","Atrovent","Ketalar","Toradol","Lidocaine","Ativan",
"Mag","Solu-Medrol","Lopressor","Versed","Duramorph","Narcan","Nitrostat",
"Nitrous","Levophed","Zofran","O2","Phenergan","Rac Epi","Zemuron","Bicarb",
"Anectine","TXA","Norcuron"]
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return RXnames.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RXcell")
cell?.textLabel!.text = RXnames[indexPath.row]
cell?.detailTextLabel!.text = RXdetail[indexPath.row]
return cell!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vcName = RXidentities[indexPath.row]
let viewController = storyboard?.instantiateViewController(withIdentifier: (vcName))
self.navigationController?.pushViewController(viewController!, animated: true)
}
}
Short of creating a whole model class, you could do something like this where I've combined your RXDetail and RXNames so as to keep the data together and lessen the chance of errors should you change the order or add/remove items. As requested I have updated this to include an array of images. I hope this helps.
class MedicationsController: UITableViewController {
var RXItems : [(name:String, detail:String, images:[UIImage])]!
override func viewDidLoad() {
RXItems = [("Acetaminophen", "Tylenol", [#imageLiteral(resourceName: "example-image"), #imageLiteral(resourceName: "example-image")]),
("Activated Charcoal", "CharcoAid", [#imageLiteral(resourceName: "example-image")])] //etc
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return RXItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RXcell")
cell?.textLabel?.text = RXItems[indexPath.row].name
cell?.detailTextLabel?.text = RXItems[indexPath.row].detail
return cell!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "showDetailsSegue", sender: indexPath.row)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "showDetailsSegue") {
let viewController = segue.destination as! DetailsViewController
viewController.images = RXItems[sender as! Int].images
viewController.title = RXItems[sender as! Int].name
}
}
}
It appears you haven't set the delegate correctly in the storyboard, however I have improved my code to both set this in code, and set the size of the images to be constrained to, at most, the width of the root view (self.view) whilst maintaining the aspect ratio:
import UIKit
import AVKit
class DetailsViewController: UIViewController, UIScrollViewDelegate {
var images : [UIImage]?
var imageViews = [UIImageView]()
#IBOutlet private weak var scrollView: UIScrollView!
private var _contentView : UIView?
var contentView: UIView {
if(_contentView == nil) {
_contentView = UIView.init(frame: CGRect.zero)
self.scrollView.addSubview(_contentView!)
}
return _contentView!
}
override func viewDidLoad() {
super.viewDidLoad()
self.scrollView.delegate = self
if let images = self.images {
var contentSize = CGSize(width: self.view.frame.size.width, height: 0)
for img in images {
let size = CGSize(width: self.view.frame.size.width, height: CGRect.infinite.height)
let rect = CGRect(origin: CGPoint.zero, size: size)
let iv = UIImageView.init(frame: CGRect(origin: CGPoint(x:0, y:contentSize.height), size: AVMakeRect(aspectRatio: img.size, insideRect: rect).size))
contentSize.height += iv.frame.size.height
iv.image = img
self.contentView.addSubview(iv)
self.imageViews.append(iv)
}
self.contentView.frame.size = contentSize
self.scrollView.contentSize = contentSize
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
var contentSize = CGSize(width: self.view.frame.size.width, height: 0)
for iv in self.imageViews {
let size = CGSize(width: size.width, height: CGRect.infinite.height)
let rect = CGRect(origin: CGPoint.zero, size: size)
iv.frame = CGRect(origin: CGPoint(x:0, y:contentSize.height), size: AVMakeRect(aspectRatio: iv.image!.size, insideRect: rect).size)
contentSize.height += iv.frame.size.height
}
self.contentView.frame.size = contentSize
self.scrollView.contentSize = contentSize
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.contentView
}
}
Then set your storyboard up like:
Only replace the UIImageView with a UIScrollView, setting the delegate and outlets accordingly.

UIStackView sizeToFit doesn't work as expected

I've configured a test playground with a UITableView and an instance of UIStackView as its header.
The stackView contains 10 UILabels.
The problem is that after calling stackView.sizeToFit() the stackView doesn't resize itself to fit all the labels and its size is zero.
I have to manually set the size of the UIStackView to fix this issue, which defeats the purpose of the UIStackView.
Here is the code of the test Xcode Playground, so you could reproduce it in your environment:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class TestTableViewController: UITableViewController {
private lazy var searchController = UISearchController(searchResultsController: nil)
private lazy var stackView: UIStackView = {
let s = UIStackView()
s.axis = .vertical
for i in 0...10 {
let l = UILabel()
l.text = "text \(i)"
s.addArrangedSubview(l)
}
return s
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reuseIdentifier")
configureTableView()
configureSearchController()
}
private func configureTableView() {
tableView.tableHeaderView = stackView
stackView.sizeToFit()
tableView.tableHeaderView?.sizeToFit() // Doesn't work!!!!!!!
tableView.tableHeaderView?.frame.size = CGSize(width: 9, height: 100)
}
private func configureSearchController() {
// searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.placeholder = NSLocalizedString("Choose template",
comment: "Choose template")
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 7
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
}
let nc = UINavigationController(rootViewController: TestTableViewController())
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = nc
Setting size manually achieves the desired result:
private func configureTableView() {
tableView.tableHeaderView = stackView
tableView.tableHeaderView?.frame.size = CGSize(width: 0, height: 400)
}
Table header views need a little extra help... This may work for you.
private func configureTableView() {
tableView.tableHeaderView = stackView
sizeHeaderToFit(tableView: tableView)
}
private func sizeHeaderToFit(tableView: UITableView) {
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var frame = headerView.frame
frame.size.height = height
headerView.frame = frame
tableView.tableHeaderView = headerView
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
}
}

How To Share Same UIView Between Multiple UITableView Cells

Objective
When the user clicks on any of the 3 blue buttons, all buttons change to the same color.
Note: this is an abstraction of a shared progress view problem, it's therefore important that only one UIView is shared (or mimicked) across my three rows
Here is a compilable Swift project:
import UIKit
class ToggleButton: UIButton {
var connectedView: UIView?
func onPress() {
self.isHidden = true
self.connectedView?.isHidden = false
}
}
class ViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView = UITableView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
var myView: UIView? = nil
var toggleBtn: ToggleButton? = nil
override func viewDidLoad() {
self.setupTableView()
}
fileprivate func setupTableView() {
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.backgroundColor = UIColor.white
self.tableView.isOpaque = true
self.view.addSubview(self.tableView)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "CellIdentifier")
let frame = CGRect(x: 10, y: 10, width: 30, height: 30)
if let view = self.myView, let btn = self.toggleBtn {
cell.addSubview(view)
cell.addSubview(btn)
} else {
let myView = UIView(frame: frame)
myView.backgroundColor = UIColor.green
myView.isHidden = true
cell.addSubview(myView)
let toggleBtn = ToggleButton(frame: frame)
toggleBtn.backgroundColor = UIColor.blue
toggleBtn.addTarget(self, action: #selector(onPress), for: .touchUpInside)
toggleBtn.connectedView = myView
cell.addSubview(toggleBtn)
}
return cell
}
#objc func onPress(_ sender: Any) {
if let button = sender as? ToggleButton {
button.onPress()
}
}
}
Any help appreciated.
The concept of UITableViewCell is made to be very independent each other.
So the only thing you can do it having a bool flag in your ViewController, then you init your 3 cells with this flags.
And finally each time the button is pressed you toggle the flag en reload your tableView.

UITableView embedded in UIView, empty rows

I am working on a new application and I have some problems:
I am developing everything programmatically, so I created a SCROLLVIEW and a CONTAINERVIEW for all my pages. containerView is embedded in scrollView.
Every page has a different Controller, in this case "SearchViewController".
The containerView contains the UIView "selectView" that belongs to "SearchViewController". See below:
let scrollView: UIScrollView = UIScrollView()
let containerView: UIView = UIView()
//SCROLLVIEW
scrollView.frame = CGRectMake(0, 0, view.frame.width, view.frame.height)
self.view.addSubview(scrollView)
//CONTAINERVIEW
containerView.frame = CGRectMake(menuWidth, 0, view.frame.width, view.frame.height)
containerView.backgroundColor = UIColor.whiteColor()
scrollView.addSubview(containerView)
let s: SearchViewController = SearchViewController()
s.setup(bannerHeight, containerViewWidth: containerView.frame.width, containerViewHeight: containerView.frame.height)
containerView.addSubview(s.selectView)
import UIKit
class SearchViewController: UIViewController, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource {
let selectView: UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
}
let table: UITableView = UITableView()
var items: [String] = ["A", "B", "C"]
var filteredItems = [String]()
func setup(bannerHeight: CGFloat, containerViewWidth: CGFloat, containerViewHeight: CGFloat){
//SELECT PAGE
selectView.frame = CGRectMake(0, bannerHeight, containerViewWidth, containerViewHeight - bannerHeight)
//UITABLEVIEW
table.frame = CGRectMake(0, 0, selectView.frame.width, selectView.frame.height)
table.delegate = self
table.dataSource = self
table.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
selectView.addSubview(table)
}
func numberOfSectionsInTableView(tableView:UITableView)->Int
{
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("tableViewCOUNT")
return self.items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
print("tableViewMAIN")
let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell")! as UITableViewCell
cell.textLabel!.text = items[indexPath.row]
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
}
}
So now i have this hierarchy: ScrollView<-containerView<-selectView<-table
The problem is that no data appear! In output I can see "tableViewCOUNT" but never "tableViewMAIN" and except for the empty rows there are no data.
Could somebody help me to understand why?
Am I using the correct way? I would like to have a mainController and for every page a subController, then I will embed the page I need inside the containerView (like an iframe in html).
Thank you in advance!
Try calling
table.reloadData()
in the setup.

Call back method not calling from UIView to ViewController in Swift

I have two classes CustomSwipOut which is a subclass of UIView and ViewController subclass of UIViewController. The callback method of delegate is not firing in ViewController class from didSelectRowAtIndexPath delegate method of table view defined in CustomSwipeOut. I have done optional binding in ViewController class as var a = CustomSwipeOut(); a.delegate = ViewController()
protocol SendIndexDelegate{
func sendIndex(Int);
}
class CustomSwipeOut: UIView , UITableViewDataSource , UITableViewDelegate {
var label: UILabel = UILabel()
var myNames = ["item1","item2","item3"]
var delegate : SendIndexDelegate?
override init()
{
super.init()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addCustomView()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addCustomView()
{
//add blank subview to the view
var blankView : UIView = UIView(frame: CGRectMake(0, 0, 300, 100))
blankView.backgroundColor = UIColor.greenColor()
self.addSubview(blankView)
//creating a tableview programmatically
var tblView : UITableView = UITableView()
tblView.frame = CGRectMake(0, 100, 300, 200)
self.addSubview(tblView)
tblView.delegate = self
tblView.dataSource = self
tblView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "myCell")
}
//pragma mark- table view data source methods
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("myCell") as UITableViewCell
cell.textLabel?.text = self.myNames[indexPath.row]
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.myNames.count
}
//pragma mark - table view delegate methods
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var row = indexPath.row
if let temp = self.delegate {
delegate?.sendIndex(row)
}else{
println("optional value contains nill value")
}
}
//ViewController Class
class ViewController: UIViewController , SendIndexDelegate {
var myView :UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var a = CustomSwipeOut()
a.delegate = ViewController()
let rect: CGRect = CGRect (x: self.view.frame.size.width, y :50 , width: self.view.frame.size.width-100, height: self.view.frame.size.height-100)
self.myView = CustomSwipeOut(frame : rect )
self.view.addSubview(self.myView)
//optional chaining
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func showSideMenu() {
UIView.animateWithDuration(0.2, animations:{
self.myView.frame = CGRectMake(100, 50,self.view.frame.size.width-100,self.view.frame.size.height-100)
} )
}
//delegate method
func sendIndex(row : Int)
{
switch row {
case 0:
println("index o clicked")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("MoneySum") as UIViewController
self.presentViewController(vc, animated: true, completion: nil)
println("index 2 clicked")
...
default:
println("no index")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let rect: CGRect = CGRect (x: self.view.frame.size.width, y :50 , width: self.view.frame.size.width-100, height: self.view.frame.size.height-100)
var a = CustomSwipeOut(frame : rect)
a.delegate = self
self.myView = a
self.view.addSubview(self.myView)
//optional chaining
}

Resources