Scrolling Delegate in TableView - swift

I want to make an animation like this link:
https://www.pinterest.com/pin/420523683937443901/sent/?sender=335307272165049646&invite_code=f63f81c77d28a48e6181db7df90b423a
Here is my simple screenshot:
In my case, I want when I swiping up, the blue Header View will disappear and the navigation bar will change to blue color.
Here is my code:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView: UITableView!
#IBOutlet var headerView: UIView!
var dataSource: [String] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14].map {"\($0)"}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
headerView.backgroundColor = UIColor.blueColor()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel!.text = dataSource[indexPath.row]
return cell
}
}
I can change the views with animation like this, include: Navigation Bar, Header View of TableView, Status Bar.
But how to check when the TableView scroll down, then the Header View will change.

You must use UIScrollViewDelegate in your tableView to intercept scrollView actions with:
class YourClass: YourType, UIScrollViewDelegate {}
Check the official apple documentation
You can handle scrollview looking for scrollViewDidScroll(_:) method.
This is just an example to add more network data when the user scroll to the end, you can use it to trigger your header animation..
let threshold = 100.0 // threshold from bottom of tableView
var isLoadingMore = false // flag
func scrollViewDidScroll(scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;
if !isLoadingMore && (maximumOffset - contentOffset <= threshold) {
// Get more data - API call
self.isLoadingMore = true
// Update UI
dispatch_async(dispatch_get_main_queue()) {
tableView.reloadData()
self.isLoadingMore = false
}
}
}
UPDATE:
By analizying your animation, it's not simple but not impossible :)
I can see two different states:
And the second state:
You can organize your controller by choose a UIViewController type.
This UIViewController must be composed by:
-UINavigationController (as you can see on the top of the images , choose if you want to embedded it or link a navigation controller and set your viewController as the navigation
rootViewController)
-UIPageView (you can use in your main viewController with the UIPageViewControllerDataSource and UIPageViewControllerDelegate, pay attention to the dimension , it cover the 30% of the top of your controller)
-UITableView (this is the last layout part, everytime page scroller the datasource can be changed and refreshed to the table)
P.S. The tableViewHeader can be the gray label with the date: Thursday 21 January 2016, as you can see the dimension dont change during animation.

Related

Retain The State Of On Off Button In A UITableViewCell - Swift

I have been searching through many posts on SO but couldn't find an answer to this one.
I have a Table view listing various items. Each cell in the table view has a button that swaps an image around when clicked on, effectively working as an "on" or "off" button to show a user which items in the list they have selected. I have a variable inside my custom Cell Prototype class which stores a value of true or false which is updated every time the button is clicked on.
There is a "Done" button in the Table View that when tapped on calls an unwind Segue to go back to the first View Controller.
When the user taps on the Enter Table View button (on the first View Controller) to display the Table the buttons all go back to their default state, am guessing because each time the segue to the Table View Screen happens it creates a new instance of the Table to be displayed.
What I'm trying to achieve is that the state of the button (either on or off) is retained when going back into the Table screen. I've tried for a while sending an Integer value back from the TableCell Class (using a delegate) to the first View controller and then passing that value back into the Table View controller when the forward segue is called in order to have a "retained from the previous state value" that can be compared against when the cells are created to indicate if a button had been clicked or not. Couldn't get it to work though to save the state of the buttons. The list of Items in the Table will also change depending on what a user adds.
Some Screen shots and the code are below. The code hasn't got the delegate i was trying included (as it didn't work) but if its needed I will edit the post to include it. Any help would be hugely appreciated. Many Thanks!!
Code I have so far is:
//Main View Controller//
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
//segue to the Table View Screen
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EnterTableView" {
let newTableView = segue.destination as! TableViewController
}
}
// Unwind Segue Called on Exit From Table View
#IBAction func unwindToMainViewController (_segue:UIStoryboardSegue) {
}
}
// Table View Controller //
class TableViewController: UITableViewController {
var dataArray = ["A", "B", "C", "D"]
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
//create one section for table
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//create number of rows based on the number of items in the dataArray set above
return dataArray.count
}
// an array that will contain all the cells
var cellArray = [UITableViewCell] ()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//create a new cell based on the cell class "TableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as? TableViewCell
// variable to contain a single element from the dataArray - updates every time a new cell is created
let dataArrayForCells = dataArray [indexPath.row]
//set cell label text to show the value from the dataArrayForCells
cell!.label.text = dataArrayForCells
cellArray.append(cell!)
return cell!
}
// Table Cell Class//
class TableViewCell: UITableViewCell {
#IBOutlet weak var label: UILabel! // label to hold display some text
#IBOutlet weak var buttonImage: UIImageView! //image for button
var buttonClicked = true //variable to contain weather the has been clicked
//if button is tapped on run the below
#IBAction func aButton(_ sender: Any) {
//if button is clicked is true, swap image to red ("on") button, set buttonClicked value to false
if buttonClicked {
buttonImage.image = #imageLiteral(resourceName: "Rec Button Red")
buttonClicked = false
}
// if buttonClicked value is false swap image to grey ("off") button set buttonClicked value back to true
else {
buttonImage.image = #imageLiteral(resourceName: "Rec Button Grey")
buttonClicked = true
}
}

Tab Bar Item hidden behind tableview / not being shown?

I have an empty view with a tab bar pictured below, when i load a routine a table appears containing the contents, however it seems to overlay the tab bar killing off app navigation. Its not sized in the storyboard to overlay it and its constraint locked to not do so, so im unsure why this is happening, pics of the issue and VC's code below:
VC Code:
import Foundation
import UIKit
import CoreData
class RoutineController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// MARK: - DECLARATIONS
#IBAction func unwindToRoutine(segue: UIStoryboardSegue) {}
#IBOutlet weak var daysRoutineTable: UITableView!
#IBOutlet weak var columnHeaderBanner: UIView!
#IBOutlet weak var todaysRoutineNavBar: UINavigationBar!
#IBOutlet weak var addTOdaysRoutineLabel: UILabel!
let date = Date()
let dateFormatter = DateFormatter()
let segueEditUserExerciseViewController = "editExerciseInRoutineSegue"
//This is the selected routine passed from the previous VC
var selectedroutine : UserRoutine?
// MARK: - VIEWDIDLOAD
override func viewDidLoad() {
super.viewDidLoad()
setupView()
daysRoutineTable.delegate = self
daysRoutineTable.dataSource = self
view.backgroundColor = (UIColor.customBackgroundGraphite())
dateFormatter.dateStyle = .short
dateFormatter.dateFormat = "dd/MM/yyyy"
let dateStr = dateFormatter.string(from: date)
todaysRoutineNavBar.topItem?.title = dateStr + " Routine"
}
// MARK: - VIEWDIDAPPEAR
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
}
// MARK: - TABLE UPDATE COMPONENTS
private func setupView() {
updateView()
}
// MARK: - TABLE SETUP
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = self.selectedroutine?.userexercises?.count
{
print("exercises: \(count)")
return count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? TodaysRoutineTableViewCell else {
fatalError("Unexpected Index Path")
}
cell.backgroundColor = UIColor.customBackgroundGraphite()
cell.textLabel?.textColor = UIColor.white
configure(cell, at: indexPath)
return cell
}
// MARK: - VIEW CONTROLER ELEMENTS VISIBILITY CONTROL
fileprivate func updateView() {
var hasUserExercises = false
if let UserExercise = self.selectedroutine?.userexercises {
hasUserExercises = UserExercise.count > 0
}
addTOdaysRoutineLabel.isHidden = hasUserExercises
columnHeaderBanner.isHidden = !hasUserExercises
daysRoutineTable.isHidden = !hasUserExercises
}
// MARK: - SETTING DATA FOR A TABLE CELL
func configure(_ cell: TodaysRoutineTableViewCell, at indexPath: IndexPath) {
if let userExercise = selectedroutine?.userexercises?.allObjects[indexPath.row]
{
print("\((userExercise as! UserExercise).name)")
cell.todaysExerciseNameLabel.text = (userExercise as! UserExercise).name
cell.todaysExerciseRepsLabel.text = String((userExercise as! UserExercise).reps)
cell.todaysExerciseSetsLabel.text = String((userExercise as! UserExercise).sets)
cell.todaysExerciseWeightLabel.text = String((userExercise as! UserExercise).weight)
}
}
}
requested table constraints
Debug hierarchy
The Segue that sends the user back to the view that looses its tab bar
if segue.identifier == "addToTodaySegue" {
let indexPath = workoutTemplateTable.indexPathForSelectedRow
let selectedRow = indexPath?.row
print("selected row\(selectedRow)")
if let selectedRoutine = self.fetchedResultsController.fetchedObjects?[selectedRow!]
{
if let todaysRoutineController = segue.destination as? RoutineController {
todaysRoutineController.selectedroutine = selectedRoutine
}
}
}
I also feel perhaps the viewDidAppear code may cause the issue, perhaps the super class?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
Updated storyboard image
I suspect you need to embed your viewController in a UINavigationController.
Consider the following setup:
I suspect your setup is like the upper one:
TapBar -> ViewController -show segue-> ViewController
Which results in a hidden tapbar, like in your description:
While the bottom setup:
TapBar -> NavigationCntroller -rootView-> ViewController -show segue-> ViewController
results in:
which is what you want, how I understood.
Update
It's hard to see. The screenshot of your Storyboard is in pretty low resulution, but the segues look wrong. Double check them. A Segue of type show (e.g push) looks like this:
Also clear project and derived data. Segue type changes sometime are ignored until doing so.
Try calling this self.view.bringSubviewToFront(YourTabControl).
The previous suggestion should work. But the content at the bottom part of tableview will not be visible as the tabbar comes over it. So set the bottom constraint of tableview as the height of tabbar.

UIView is always behind UITableView

I have a UIViewController (not a UITableViewController because I read that a view controller is best for this kind of behaviour) with a UITableView. In this view controller I want to add a floating UIView and place it above my tableview, to do so I wrote:
public override func viewDidLoad() {
super.viewDidLoad()
// add button
let fbv = liquidActionButtonInstance.addActionButton() // this is a UIView
self.view.addSubview(fbv)
liquidActionButtonInstance.delegate = self
// delegate
tableView.delegate = self
tableView.dataSource = self
}
However my floating view appears behind my UITableView, how can I add it as the first child of self.view? I've used
self.view.addSubview(fbv)
self.view.bringSubviewToFront(fbv)
Among others and none seems to work.
Edit:
I added some screenshots of my view's hierarchy.
Edit 2:
Here I put a little more code:
My ViewController without some unrelated code:
public class ActividadesTableViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var descripcionFiltrosLabel: UILabel!
#IBOutlet weak var filtrosLabelBottomConstraint: NSLayoutConstraint!
#IBOutlet weak var filtrosLabelTopConstraint: NSLayoutConstraint!
#IBOutlet weak var tableViewTopConstraint: NSLayoutConstraint!
private var liquidActionButtonInstance: FloatingActionButton = FloatingActionButton()
public var viewModel : ActividadesTableViewModeling?
public override func viewDidLoad() {
super.viewDidLoad()
// Agregar action button
self.view.insertSubview(liquidActionButtonInstance.addActionButton(),aboveSubview: tableView)
liquidActionButtonInstance.delegate = self
// set row's height
tableView.estimatedRowHeight = 70
tableView.rowHeight = UITableViewAutomaticDimension
// delegate
tableView.delegate = self
tableView.dataSource = self
// load tableview data
if let viewModel = viewModel {
viewModel.loadActividades(withFilters: nil)
}
}
}
// MARK: FloatingButton
extension ActividadesTableViewController: FloatingActionButtonDelegate {
public func performSegueFromFloatingActionButton(segueName name: String) {
performSegueWithIdentifier(name, sender: self)
}
}
// MARK: TableView
extension ActividadesTableViewController: UITableViewDataSource, UITableViewDelegate {
public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Omitting this code, just mentioning the methods
}
public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ActividadCell", forIndexPath: indexPath) as! ActividadTableViewCell
if let viewModel = viewModel {
cell.viewModel = viewModel.cellModels.value[indexPath.row]
} else {
cell.viewModel = nil
}
return cell
}
public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
}
And this is how I add the button (in a different class):
func addActionButton() -> LiquidFloatingActionButton {
let createButton: (CGRect, LiquidFloatingActionButtonAnimateStyle) -> LiquidFloatingActionButton = { (frame, style) in
let floatingActionButton = LiquidFloatingActionButton(frame: frame)
floatingActionButton.animateStyle = style
floatingActionButton.dataSource = self
floatingActionButton.delegate = self
floatingActionButton.color = ColoresKairos.principal2
return floatingActionButton
}
let cellFactory: (String) -> LiquidFloatingCell = { (iconName) in
return LiquidFloatingCell(icon: UIImage(named: iconName)!)
}
cells.append(cellFactory("iphone-action-button-group"))
cells.append(cellFactory("iphone-action-button-notepad"))
cells.append(cellFactory("iphone-action-button-check-box"))
let floatingFrame = CGRect(x: UIScreen.mainScreen().bounds.width - 56 - 16, y: UIScreen.mainScreen().bounds.height - 56 - 16, width: 56, height: 56)
let bottomRightButton = createButton(floatingFrame, .Up)
//view.addSubview(bottomRightButton)
//return view
return bottomRightButton
}
You need to add the view using addSubview(_:) AND bringToFront(_:). You can also try sending the tableView to the back using sendToBack(_:)
In your document outline menu in your storyboard, you can place your floating view below your TableView in view hierarchy. Then, you can always see your floating view placed above your tableview.
It's gonna be like this.
▼ Your ViewController
Top Layout Guide
Bottom Layout Guide
▼View
▶︎ TableView
▶︎ Floating View
Below is the code that is working along with its screenshot, I suspect that your call liquidActionButtonInstance.addActionButton() returns an empty button?
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
private var liquidButton: LiquidFloatingActionButton?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// Setting up the liquid button
liquidButton = createLiquidButton()
view.addSubview(liquidButton!)
}
func createLiquidButton() -> LiquidFloatingActionButton {
let frame = CGRect(x: UIScreen.mainScreen().bounds.width - 56 - 16, y: UIScreen.mainScreen().bounds.height - 56 - 16, width: 56, height: 56)
let button = LiquidFloatingActionButton(frame: frame)
button.animateStyle = .Up
button.color = UIColor.redColor()
return button
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
// UITableView Implementation, not included not relevent for the example.
}
The error here, after debugging the view hierarchy was that my tableView wasn't being added directly to the UIViewController's hierarchy, so it was added above everything else, and adding my FloatingButton as a subview of UIViewController always resulted on it being hidden by my tableView.
My tableView is added via storyboard, and as far as I know this is not the expected behaviour, but in my case just adding the following lines:
self.view.addSubview(tableView)
self.view.addSubview(liquidActionButtonInstance.addActionButton())
Solved my problem.

UIview kept going at the bottom of the UIViewcontroller

My LoginViewController has a UIView that has 2 UItextfield and 1 UIbutton. The moment the user start writting the UIView should go up and leave space for the keyboard. However my problem is when the keyboard disappear the UIView did not go at its initial position. Can anyone help me please Thank you ... (my code is below)
func keyboardON(sender: NSNotification) {
let info = sender.userInfo!
var keyboardSize = info[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue().size
println("keyboard height \(keyboardSize.height)")
var frame = otherContainerView.frame
println("MainScreen :\(UIScreen.mainScreen().bounds.height)")
frame.origin.y = UIScreen.mainScreen().bounds.height - keyboardSize.height - frame.size.height
otherContainerView.frame = frame
}
func keyboardNotOn(sender: NSNotification) {
let info = sender.userInfo!
var keyboardSize = info[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue().size
println("keyboard height \(keyboardSize.height)")
var frame = otherContainerView.frame
frame.origin.y = UIScreen.mainScreen().bounds.height - frame.size.height
otherContainerView.frame = frame
}
I would suggest that you change this view controller to a UITableViewController with static cells. When using a UITableViewController, the scrolling is automatically handled, thus making your problem not problem at all.
If I understand your initial question correctly, you are trying to create a login screen in a UIViewController, which is fine, but much harder than it would be to simply create a UITableViewController. The image above was made with a UITableViewController. When the text fields are selected, it slides up, and when they are deselected, it moves back to it's initial view. If you switch to a UITableViewController, (even if you place both UITextFields and the button in one cell), you won't need to do any of this programmatically. The storyboard will handle the desired changes.
import UIKit
class LoginTableViewController: UITableViewController {
#IBOutlet weak var usernameTextField : UITextField!
#IBOutlet weak var passwordTextField : UITextField!
#IBAction func login (sender: UIButton) {
//login button
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//If you choose to use 3 cells in your storyboard (like I've done with my login screen) you will return 3 here. If you choose to put all of the items in one cell, return 1 below.
return 3
}
}

how to create xib subview at the center of tableview swift

I have tableview and I created the custom xib uiview as "detailview" for it. I want to show this detailview at the center of scrolled area when tapped to tableview cell. I can show this view but cannot centralized it. when I set value to frame manually, subview will be at center (approximately) but when I tap the cell which is at the bottom, the subview is appearing at the top of page and also it is moving when i scroll the tableview.
Please help me to show this view at the center of the scrolled area and be fixed
Here is my codes;
Detail View :
class TopTenDetailView: UIView {
var screenWidth:CGFloat = UIScreen.mainScreen().bounds.width*0.08
var screenHeight :CGFloat = UIScreen.mainScreen().bounds.height*0.08
class func instanceFromNib() -> UIView {
return UINib(nibName: "TopTenDetail", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}
override func awakeFromNib() {
super.awakeFromNib()
self.layer.cornerRadius=10
let testFrame : CGRect = CGRectMake(screenWidth,screenHeight,320,480)
self.frame = testFrame
self.userInteractionEnabled=true
}
#IBAction func close(sender: UIButton) {
self.hidden=true
}
}
And TableViewController's method ;
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var detailView = TopTenDetailView.instanceFromNib
self.view.addSubview(detailView())
}
The way this is setup has many problems and i would be surprised if it actually ever works as intended.
A much better, simpler setup uses a OverFullScreen presentation style and it goes like this:
Create a separate UIViewController for your detail view, let's call it DetailViewController use Interface Builder. Make sure to set the background color to CLEAR
Wire up a segue from the "base" UIViewController that holds your UITableView to DetailViewController and give the segue a name. Let's call it 'detailSegue' , basically drag from one view controller to the other. Make sure that you are not dragging from the view but from the yellow icon at the top of the view controller. You are done in Interface Builder.
Ok, now for the code:
// MARK : - UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("detailSegue", sender: self)
}
// MARK: - segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let vc = segue.destinationViewController as? UIViewController{
vc.modalPresentationStyle = .OverFullScreen
}
}
The OverFullScreen presentation style uses a proper UIViewController modal segue but leaves the presenting UIViewController visible under the presented one.
You can then just layout whatever you want on DetailViewController using Interface Builder and autolayout without having to do hacky match calculations on the layout at runtime.
Hope it helps!