I have a weird issue when creating the Slide out menu in swift,
I'm building the menu using AKSwiftSlideMenu as a reference,
This only happens on the ViewControllers that have a UITableViewDataSource, UITableViewDelegate.
If I go to the other view controllers without one, the menu shows up fine.
Below is my code for BaseViewController
class BaseViewController: UIViewController, SlideMenuDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func slideMenuItemSelectedAtIndex(_ index: Int32) {
let topViewController : UIViewController = self.navigationController!.topViewController!
print("View Controller is : \(topViewController) \n", terminator: "")
switch(index){
case 0:
print("Locations\n", terminator: "")
self.openViewControllerBasedOnIdentifier("Locations")
break
case 1:
print("Offers\n", terminator: "")
self.openViewControllerBasedOnIdentifier("Offers")
break
case 2:
print("Feedback\n", terminator: "")
self.openViewControllerBasedOnIdentifier("Feedback")
break
case 3:
print("About\n", terminator: "")
self.openViewControllerBasedOnIdentifier("About")
break
case 4:
for key in UserDefaults.standard.dictionaryRepresentation().keys {
UserDefaults.standard.removeObject(forKey: key)
}
//fb logout
if(FBSDKAccessToken.current() != nil) {
FBSDKAccessToken.setCurrent(nil)
FBSDKProfile.setCurrent(nil)
}
self.openViewControllerBasedOnIdentifier("SocialLogin")
default:
print("default\n", terminator: "")
}
}
func openViewControllerBasedOnIdentifier(_ strIdentifier:String){
let destViewController : UIViewController = self.storyboard!.instantiateViewController(withIdentifier: strIdentifier)
let topViewController : UIViewController = self.navigationController!.topViewController!
if (topViewController.restorationIdentifier! == destViewController.restorationIdentifier!){
print("Same VC")
} else {
self.navigationController!.pushViewController(destViewController, animated: true)
}
}
func addSlideMenuButton(){
let btnShowMenu = UIButton(type: UIButtonType.system)
btnShowMenu.setImage(self.defaultMenuImage(), for: UIControlState())
btnShowMenu.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
btnShowMenu.addTarget(self, action: #selector(BaseViewController.onSlideMenuButtonPressed(_:)), for: UIControlEvents.touchUpInside)
let customBarItem = UIBarButtonItem(customView: btnShowMenu)
self.navigationItem.leftBarButtonItem = customBarItem;
}
func defaultMenuImage() -> UIImage {
var defaultMenuImage = UIImage()
UIGraphicsBeginImageContextWithOptions(CGSize(width: 30, height: 22), false, 0.0)
UIColor.black.setFill()
UIBezierPath(rect: CGRect(x: 0, y: 3, width: 30, height: 1)).fill()
UIBezierPath(rect: CGRect(x: 0, y: 10, width: 30, height: 1)).fill()
UIBezierPath(rect: CGRect(x: 0, y: 17, width: 30, height: 1)).fill()
UIColor.white.setFill()
UIBezierPath(rect: CGRect(x: 0, y: 4, width: 30, height: 1)).fill()
UIBezierPath(rect: CGRect(x: 0, y: 11, width: 30, height: 1)).fill()
UIBezierPath(rect: CGRect(x: 0, y: 18, width: 30, height: 1)).fill()
defaultMenuImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return defaultMenuImage;
}
func onSlideMenuButtonPressed(_ sender : UIButton){
if (sender.tag == 10)
{
// To Hide Menu If it already there
self.slideMenuItemSelectedAtIndex(-1);
sender.tag = 0;
let viewMenuBack : UIView = view.subviews.last!
UIView.animate(withDuration: 0.3, animations: { () -> Void in
var frameMenu : CGRect = viewMenuBack.frame
frameMenu.origin.x = -1 * UIScreen.main.bounds.size.width
viewMenuBack.frame = frameMenu
viewMenuBack.layoutIfNeeded()
viewMenuBack.backgroundColor = UIColor.clear
}, completion: { (finished) -> Void in
viewMenuBack.removeFromSuperview()
})
return
}
sender.isEnabled = false
sender.tag = 10
let menuVC : MenuViewController = self.storyboard!.instantiateViewController(withIdentifier: "MenuViewController") as! MenuViewController
menuVC.btnMenu = sender
menuVC.delegate = self
self.view.addSubview(menuVC.view)
self.addChildViewController(menuVC)
menuVC.view.layoutIfNeeded()
menuVC.view.frame=CGRect(x: 0 - UIScreen.main.bounds.size.width, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height);
UIView.animate(withDuration: 0.3, animations: { () -> Void in
menuVC.view.frame=CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height);
sender.isEnabled = true
}, completion:nil)
}
}
MenuViewController
protocol SlideMenuDelegate {
func slideMenuItemSelectedAtIndex(_ index : Int32)
}
class MenuViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
/**
* Array to display menu options
*/
#IBOutlet var tblMenuOptions : UITableView!
/**
* Transparent button to hide menu
*/
#IBOutlet var btnCloseMenuOverlay : UIButton!
/**
* Array containing menu options
*/
var arrayMenuOptions = [Dictionary<String,String>]()
/**
* Menu button which was tapped to display the menu
*/
var btnMenu : UIButton!
/**
* Delegate of the MenuVC
*/
var delegate : SlideMenuDelegate?
override func viewDidLoad() {
super.viewDidLoad()
tblMenuOptions.tableFooterView = UIView()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateArrayMenuOptions()
}
func updateArrayMenuOptions(){
arrayMenuOptions.append(["title":"Locations", "icon":"LocationIcon"])
arrayMenuOptions.append(["title":"Offers", "icon":"OffersIcon"])
arrayMenuOptions.append(["title":"Feedback", "icon":"FeedbackIcon"])
arrayMenuOptions.append(["title":"About", "icon":"AboutIcon"])
arrayMenuOptions.append(["title":"Logout", "icon":"LogoutIcon"])
tblMenuOptions.reloadData()
}
#IBAction func onCloseMenuClick(_ button:UIButton!){
btnMenu.tag = 0
if (self.delegate != nil) {
var index = Int32(button.tag)
if(button == self.btnCloseMenuOverlay){
index = -1
}
delegate?.slideMenuItemSelectedAtIndex(index)
}
UIView.animate(withDuration: 0.3, animations: { () -> Void in
self.view.frame = CGRect(x: -UIScreen.main.bounds.size.width, y: 0, width: UIScreen.main.bounds.size.width,height: UIScreen.main.bounds.size.height)
self.view.layoutIfNeeded()
self.view.backgroundColor = UIColor.clear
}, completion: { (finished) -> Void in
self.view.removeFromSuperview()
self.removeFromParentViewController()
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cellMenu")!
cell.selectionStyle = UITableViewCellSelectionStyle.none
cell.layoutMargins = UIEdgeInsets.zero
cell.preservesSuperviewLayoutMargins = false
cell.backgroundColor = UIColor.clear
let lblTitle : UILabel = cell.contentView.viewWithTag(101) as! UILabel
let imgIcon : UIImageView = cell.contentView.viewWithTag(100) as! UIImageView
imgIcon.image = UIImage(named: arrayMenuOptions[indexPath.row]["icon"]!)
lblTitle.text = arrayMenuOptions[indexPath.row]["title"]!
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let btn = UIButton(type: UIButtonType.custom)
btn.tag = indexPath.row
self.onCloseMenuClick(btn)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayMenuOptions.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1;
}
}
a example of view controller where this happens
class LocationViewController: BaseViewController, CLLocationManagerDelegate, UITableViewDataSource, UITableViewDelegate {
var locItems:Array<LocItems>?
var locItemsWrapper:LocItemsWrapper?
var isLoadingLocItems = false
private var tb: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
//set up tableview
tb = UITableView()
//Set button to open up menu
self.addSlideMenuButton()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.locItems == nil) {
return 0
}
return self.locItems!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "locCell", for: indexPath) as! LocItemCell
if(self.locItems != nil && self.locItems!.count >= indexPath.row) {
let locItem = self.locItems![indexPath.row]
let rowsLoaded = self.locItems!.count
if (!self.isLoadingLocItems && (indexPath.row >= (rowsLoaded - rowsToLoadFromBottom))) {
let totalRows = self.locItemsWrapper!.count!
let remainingLocItemsToLoad = totalRows - rowsLoaded
if(remainingLocItemsToLoad > 0) {
self.loadMoreLocItems()
}
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if(self.locItems!.count >= indexPath.row) {
selectedLocId = self.locItems![indexPath.row].id!
selectedLocBg = self.locItems![indexPath.row].locationBackground!
//TODO: Set up Switch on api call
let parameters = [
"locId": selectedLocId
]
}
}
override func prepare(for segue: (UIStoryboardSegue!), sender: Any!) {
if(segue.identifier == "showCongratOffer") {
let svc = segue.destination as! CongratOfferViewController
svc.offerId = selectedLocId
svc.type = 1
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if((indexPath.row % 2) == 0) {
cell.backgroundColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0)
} else {
cell.backgroundColor = UIColor.white
}
}
I know it's a lot of code, I tried to cut out pieces in the example where it happens as much as possible while leaving the menu code complete,
I would really appreciate if someone could help me with this issue or point me in the right direction.
A picture of the tableview before I open the menu,
A picture of the storyboard,
The top left controller can be ignored,
from login (top middle), we go to locations (bottom right, bottom middle) where the menu exists,
about (top right) will show the menu properly without the blank space,
the menu controller is bottom left.
The menu still works, though the empty space shows up in controllers with a UITableViewDataSource, UITableViewDelegate.
UPDATE: Looking at the view and how it gets built, I've found the menu height constraint is different. It's not getting set after menuVC.view.layoutIfNeeded() in BaseViewController.
print(self.childViewControllers[0].topLayoutGuide)
Empty Space Issue -> <_UILayoutGuide: 0x7fce6e7115e0; frame = (0 0; 0 0); hidden = YES; layer = <CALayer: 0x600000238700>>
Working -> <_UILayoutGuide: 0x7fce6e51ad30; frame = (0 0; 0 64); hidden = YES; layer = <CALayer: 0x60000023c9a0>>
How would you change just the height constraint on the view?
I can get the read-only view constraints like so,
self.childViewControllers[0].view.constraints
I see the issue now. What you need to do is copy the cell from your TableView, delete the original cell, make sure the TableView is completely empty, and paste the Cell back in. You can do this manually as well by making a new cell entirely. BTW, the issue is that there is an empty space above your cell which you need to remove from your TableView.
Related
My Viewcontroller shows "Cannot find 'selectedIndexPaths' in scope" and other errors
My viewcontroller with errors:- import UIKit
class ProductViewController: UIViewController, UITableViewDataSource,
UITableViewDelegate {
let notificationButton = SSBadgeButton()
let rightbarbuttonimage = UIImage(named:"ic_cart")
fileprivate var cart = Cart()
let scrollView = UIScrollView()
let sections = ["Section A", "Section B","Section C", "Section D","Section E","Section F","Section G","Section H", "Section I","Section J","Section K","Section L"]
let rowspersection = [2,3,1,2,2,3,3,1,4,2,1,2]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
self.tableView.backgroundColor = UIColor.gray
//Add and setup scroll view
self.tableView.addSubview(self.scrollView)
self.scrollView.translatesAutoresizingMaskIntoConstraints = false;
//Constrain scroll view
self.scrollView.leadingAnchor.constraint(equalTo: self.tableView.leadingAnchor, constant: 20).isActive = true;
self.scrollView.topAnchor.constraint(equalTo: self.tableView.topAnchor, constant: 20).isActive = true;
self.scrollView.trailingAnchor.constraint(equalTo: self.tableView.trailingAnchor, constant: -20).isActive = true;
self.scrollView.bottomAnchor.constraint(equalTo: self.tableView.bottomAnchor, constant: -20).isActive = true;
// customising rightBarButtonItems as notificationbutton
notificationButton.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
notificationButton.setImage(UIImage(named: "ic_cart")?.withRenderingMode(.alwaysTemplate), for: .normal)
notificationButton.badgeEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 15)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: notificationButton)
//following register is needed because I have rightbarbuttonitem customised as uibutton i.e. notificationbutton
notificationButton.addTarget(self, action: #selector(self.registerTapped(_:)), for: .touchUpInside)
}
#objc func registerTapped(_ sender: UIButton) {
self.performSegue(withIdentifier: "showCart", sender: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//Workaround to avoid the fadout the right bar button item
self.navigationItem.rightBarButtonItem?.isEnabled = false
self.navigationItem.rightBarButtonItem?.isEnabled = true
//Update cart if some items quantity is equal to 0 and reload the product table and right button bar item
cart.updateCart()
//self.navigationItem.rightBarButtonItem?.title = "Checkout (\(cart.items.count))"
notificationButton.badge = String(cart.items.count)// making badge equal to no.ofitems in cart
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// this segue to transfer data
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showCart" {
if let cartViewController = segue.destination as? CartViewController {
cartViewController.cart = self.cart
}
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return productMap.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productMap[section]?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let product = productMap[indexPath.section]![indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ProductTableViewCell") as! ProductTableViewCell
cell.imageView?.image = product.imagename
cell.delegate = self as CartDelegate
cell.setButton(state: self.cart.contains(product: product))
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch(section) {
case 0: return "Section A"
case 1: return "Section B"
case 2: return "Section C"
case 3: return "Section D"
case 4: return "Section E"
case 5: return "Section F"
case 6: return "Section G"
case 7: return "Section H"
case 8: return "Section I"
case 9: return "Section J"
case 10: return "Section K"
case 11: return "Section L"
default: return ""
}
}
}
extension ProductViewController: CartDelegate {
// MARK: - CartDelegate
func updateCart(cell: ProductTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let product = productMap[indexPath.section]![indexPath.row]
/// `var selectedIndexPaths = [IndexPath]()` defined inside `ProductViewController`, to keep track of the selected products// **error -Cannot find 'selectedIndexPaths' in scope**
if selectedIndexPaths.contains(indexPath) {**//error -Cannot find 'selectedIndexPaths' in scope**
if let index = selectedIndexPaths.firstIndex(of: indexPath) {
selectedIndexPaths.remove(at: index)**//error -Cannot find 'selectedIndexPaths' in scope**
removeProductFromCart(indexPath: indexPath)
}
} else {
selectedIndexPaths.append(indexPath)
addProductToCart(indexPath: indexPath)
}
// addProductToCart(indexPath: indexPath)
/// **I commented this out because I don't have the code for `Cart`**
//Update Cart with product
// cart.updateCart(with: product)
// self.navigationItem.rightBarButtonItem?.title = "Checkout (\(cart.items.count))"
// notificationButton.badge = String(cart.items.count) // making badge equal to noofitems in cart
}
}
func addProductToCart(indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? ProductTableViewCell {
if let imageView = cell.imagename {
let initialImageViewFrame = imageView.convert(imageView.frame, to: self.view)
let targetImageViewFrame = self.notificationButton.frame
let imgViewTemp = UIImageView(frame: initialImageViewFrame)
imgViewTemp.clipsToBounds = true
imgViewTemp.contentMode = .scaleAspectFill
imgViewTemp.image = imageView.image
self.view.addSubview(imgViewTemp)
UIView.animate(withDuration: 1.0, animations: {
imgViewTemp.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}) { _ in
UIView.animate(withDuration: 0.5, animations: {
imgViewTemp.transform = CGAffineTransform(scaleX: 0.2, y: 0.2).rotated(by: CGFloat(Double.pi))
imgViewTemp.frame = targetImageViewFrame
}) { _ in
imgViewTemp.removeFromSuperview()
UIView.animate(withDuration: 1.0, animations: {
self.notificationButton.transform = CGAffineTransform(scaleX: 1.4, y: 1.4)
}, completion: {_ in
self.notificationButton.transform = CGAffineTransform.identity
})
}
}
}
}
}
func removeProductFromCart(indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? ProductTableViewCell {
if let imageView = cell.imagename {
let initialImageViewFrame = self.notificationButton.frame
let targetImageViewFrame = imageView.convert(imageView.frame, to: self.view)
let imgViewTemp = UIImageView(frame: initialImageViewFrame)
imgViewTemp.clipsToBounds = true
imgViewTemp.contentMode = .scaleAspectFill
imgViewTemp.image = imageView.image
self.view.addSubview(imgViewTemp)
var initialTransform = CGAffineTransform.identity
initialTransform = initialTransform.scaledBy(x: 0.2, y: 0.2)
initialTransform = initialTransform.rotated(by: CGFloat(Double.pi))
UIView.animate(withDuration: 0.5, animations: {
self.notificationButton.animationZoom(scaleX: 1.4, y: 1.4)
imgViewTemp.transform = initialTransform
}) { _ in
UIView.animate(withDuration: 1, animations: {
self.notificationButton.animationZoom(scaleX: 1, y: 1)
imgViewTemp.transform = CGAffineTransform.identity
imgViewTemp.frame = targetImageViewFrame
}) { _ in
imgViewTemp.removeFromSuperview()
}
}
}
}
}
screenshot of the error-
I have tableview in my productviewcotroller as shown below. so, thats not an issusem still errors:-
There are other errors also which it is showing.
How to sort it out ?
var selectedIndexPaths = IndexPath
Please define selectedIndexPaths inside the main class not in the extension then check it again .
I added an arrow to fold the section when clicked, when I fold the last section (e.g. setting the row height to 0 in cellForRow, all the rows in the last section get mixed up, as can be seen in the image below:
Can anyone suggest any reason why this should happen if I'm setting the height of the row to 0?
Here's the relevant code:
viewForHeaderInSection:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
let profileCompletion = clientFields?.profileCompletion ?? 0
headerView.profileComplete.text = "Profile".localized() + " \(profileCompletion)% " + "Complete".localized()
headerView.profileProgress.progress = Float(profileCompletion) * 0.01
headerView.profileProgress.progressTintColor = Constants.Client.getProfileCompletenessColor(profileCompletion)
headerView.delegate = self
headerView.section = section
headerView.isOpen = self.sectionsStatuses[section].shouldDisplaySection
return headerView
}
else {
let height = (section == 0) ? FIRST_HEADER_HEIGHT : HEADER_HEIGHT
let nib = UINib(nibName: "ClientDetailsSectionHeaderCell", bundle: nil)
guard let returnedView = nib.instantiate(withOwner: self, options: nil)[0] as? ClientDetailsSectionHeaderCell else {
return UIView()
}
returnedView.delegate = self
returnedView.section = section
returnedView.isOpen = self.sectionsStatuses[section].shouldDisplaySection
returnedView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: height)
returnedView.heightConstraint.constant = height
returnedView.mainView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
returnedView.mainView.backgroundColor = Constants.ParentForm.headerBgColor
// Draw separators
if DRAW_SECTION_SEPARATORS || DRAW_SECTION_TOP_SEPARATOR {
returnedView.createTopSeparator(color: Constants.ParentForm.separatorColor)
}
if DRAW_SECTION_SEPARATORS || DRAW_SECTION_BOTTOM_SEPARATOR {
returnedView.createBottomSeparator(color: Constants.ParentForm.separatorColor)
}
if isSectionTitleHidden == false {
let xOffset = HEADER_X_OFFSET
let yOffset = section == 0 ? FIRST_HEADER_Y_OFFSET : HEADER_Y_OFFSET
returnedView.title.frame = CGRect(x: xOffset, y: yOffset, width: view.frame.size.width - xOffset, height: height - yOffset)
returnedView.title.text = self.sectionsArray[section].uppercased().localized()
returnedView.title.font = Constants.ParentForm.headerFont
returnedView.title.textColor = Constants.ParentForm.headerTextColor
returnedView.title.sizeToFit()
}
return returnedView
}
}
heightForRow
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if sectionsStatuses[indexPath.section].shouldDisplaySection {
return super.tableView(tableView, heightForRowAt: indexPath)
}
return CGFloat(0)
}
cellForRow
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if self.sectionsArray[indexPath.section] != "ADDRESSES" {
if let clientFields = self.clientFields {
self.dataMap = Utils.convertObjectToMap(item: clientFields)
let birthdayDate = clientFields.birthdayDateString
self.dataMap["birthdayDate"] = birthdayDate
let createdDate = clientFields.createdDateString
self.dataMap["createdDate"] = createdDate
}
}
else {
if let clientAddresses = self.clientAddresses, clientAddresses.count > 0 {
self.dataMap = Utils.convertObjectToMap(item: clientAddresses[Int(floor(Double(indexPath.row) / 8.0))])
}
}
return super.tableView(tableView, cellForRowAt: indexPath)
}
protocol function
func openCloseSection(openSection: Bool, section: Int) {
self.sectionsStatuses[section].shouldDisplaySection = openSection
self.tableView.reloadData()
}
ClientDetailsSectionHeaderCell
import UIKit
protocol SectionViewerProtocol {
func openCloseSection(openSection: Bool, section: Int)
}
class ClientDetailsSectionHeaderCell: UITableViewCell {
#IBOutlet weak var mainView: UIView!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
#IBOutlet weak var accessoryButton: UIButton!
#IBOutlet weak var title: UILabel!
var section = 0
var delegate: SectionViewerProtocol?
var isOpen: Bool? {
didSet {
if let isOpen = self.isOpen {
accessoryButton.setImage(UIImage(named: isOpen ? "fill588": "fill589"), for: .normal)
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
#IBAction func openCloseSection(_ sender: UIButton) {
if let isOpen = self.isOpen {
self.isOpen = !isOpen
delegate?.openCloseSection(openSection: !isOpen, section: section)
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Try setting clipsToBounds of your cell or any enclosing UIView to true.
I am creating a wall page like twitter or Facebook in which user can post on the wall it can include image and just text. So based on the content the cell's heights must change, if the post is of one line the row height must be appropriate.
I tried using UITableViewAutomaticDimensions, but its not working with my code please do help me.
import UIKit
class wallPageViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var tableOfApps : UITableView?
var appNames: [String]? = []
var apps: [wallPageModel]?
var dynamic_height : CGFloat = 150.0
var addPost : UITextField?
var readMore : UITextView?
func loadTableOfApps(){
//appNames = ["jkbajdba", "abfajfb", "akjbfkjag", "ahbfkajf" ]
let provider = wallPageProvider()
apps = provider.getApps()
let tableHeight = view.frame.size.height
tableOfApps = UITableView(frame: CGRect(x: 10, y: 60, width: 300, height: tableHeight))
tableOfApps!.delegate = self
tableOfApps!.dataSource = self
tableOfApps?.separatorStyle = UITableViewCellSeparatorStyle.none
//self.tableOfApps?.rowHeight = UITableViewAutomaticDimension
//self.tableOfApps?.estimatedRowHeight = UITableViewAutomaticDimension
print("load table of apps")
view.addSubview(tableOfApps!)
}
override func viewDidLoad() {
super.viewDidLoad()
loadTableOfApps()
loadAddPost()
tableOfApps?.estimatedRowHeight = 150.0
tableOfApps?.rowHeight = UITableViewAutomaticDimension
view.backgroundColor = UIColor(red: 232/255, green: 234/255, blue: 246/255, alpha: 1.0)
// Do any additional setup after loading the view.
}
func loadAddPost(){
addPost = UITextField(frame: CGRect(x: 10, y: 20, width: 300, height: 30))
addPost?.placeholder = "Stay safe, post a tip!"
addPost?.isUserInteractionEnabled = true
addPost?.borderStyle = UITextBorderStyle.roundedRect
addPost?.autocapitalizationType = .none
view.addSubview(addPost!)
addPost?.addTarget(self, action: #selector(writeComment), for: UIControlEvents.touchDown)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("app coun\(apps!.count)")
return apps!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell : wallModelTableViewCell?
cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? wallModelTableViewCell
if cell == nil {
cell = wallModelTableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
}
let app = apps![indexPath.row]//reading the array of data hence provider
//association of provider and view
//this is the processs from where view can read the data from provide
cell!.iconImageLabel!.image = app.icon
cell!.titleLabel!.text = app.title
cell?.backgroundColor = UIColor.white
cell!.commentsLabel?.image = app.comments_Label
cell?.commentsLabel?.center = CGPoint(x: tableView.frame.size.width/2, y: dynamic_height-20)
cell?.likeButtonLabel?.image = app.likeButton
cell?.likeButtonLabel?.center = CGPoint(x: (tableView.frame.size.width/2)-130, y: dynamic_height-20)
/*cell?.likeButtonLabel?.leftAnchor.constraint(equalTo: tableView.leftAnchor, constant: 50)*/
cell!.feedTextLabel!.text = app.feedText
cell?.feedTextLabel?.sizeToFit()//so that it can expand the data more than two lines
cell?.feedTextLabel?.backgroundColor = UIColor.clear
//Limiting UIlabel to 125 characters
if (cell?.feedTextLabel?.text.count)! > 125{
/*
let index = cell?.feedTextLabel?.text.index((cell?.feedTextLabel?.text.startIndex)!, offsetBy: 125)
cell?.feedTextLabel?.text = cell?.feedTextLabel?.text.substring(to: index!)
cell?.feedTextLabel?.text = cell?.feedTextLabel?.text.appending("...")*/
//cell?.feedTextLabel?.attributedText = NSAttributedString(string: "...readmore")
}
cell?.layer.cornerRadius = 6.0
cell?.isUserInteractionEnabled = false
cell?.titleLabel?.isUserInteractionEnabled = true
tableOfApps?.backgroundColor = UIColor.clear
tableOfApps?.backgroundView?.isOpaque = false
tableOfApps!.estimatedRowHeight = 150.0
tableOfApps!.rowHeight = UITableViewAutomaticDimension
print("table view cell for row")
/*let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector( self.labelAction(gesture:)))
cell?.feedTextLabel?.addGestureRecognizer(tap)
cell?.feedTextLabel?.isUserInteractionEnabled = true
tap.delegate = self as! UIGestureRecognizerDelegate*/
return cell!
}
/*#objc func labelAction(gesture: UITapGestureRecognizer){
}*/
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
print("height for row")
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 150.0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Navigation
#objc func writeComment(){
let addComment = addCommentViewController()
navigationController?.pushViewController(addComment, animated: true)
}
/*
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
I want each row must have dynamic height as the content is provided by the user if the content contains 150 characters the height of the cell must be 200+ if content contains less than 125 characters the height must be as adequate to read.
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.
I have added a table view, and I am display image in the cells. I have also added this code:
So that the cells resize depending on the image.
When I launch my app though, I get this :
[]
And the images do not load untill I start scrolling...If I scroll down half the page then go back to the top, I get this: Which is correct
Any ideas? I have researched on google and tried the odd solution for the older versions of Xcode, But nothing seems to work!
Here is the rest of my code from the TableViewController:
extension TimelineViewController: UITableViewDataSource {
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 46
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return timelineComponent.content.count
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("PostHeader") as! PostHeaderTableViewCell
let post = self.timelineComponent.content[section]
headerCell.post = post
return headerCell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PostCell") as! PostTableViewCell
//cell.postImageView?.image = UIImage(named: "Background.png")
let post = timelineComponent.content[indexPath.section]
post.downloadImage()
post.fetchLikes()
cell.post = post
cell.layoutIfNeeded()
return cell
}
}
extension TimelineViewController: UITableViewDelegate {
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
timelineComponent.targetWillDisplayEntry(indexPath.section)
}
Download image code:
func downloadImage() {
// 1
image.value = Post.imageCache[self.imageFile!.name]
if image is not downloaded yet, get it
if (image.value == nil) {
imageFile?.getDataInBackgroundWithBlock { (data: NSData?, error: NSError?) -> Void in
if let data = data {
let image = UIImage(data: data, scale: 2.0)!
self.image.value = image
// 2
Post.imageCache[self.imageFile!.name] = image
}
}
}
}
// MARK: PFSubclassing
extension Post: PFSubclassing {
static func parseClassName() -> String {
return "Post"
}
override class func initialize() {
var onceToken : dispatch_once_t = 0;
dispatch_once(&onceToken) {
// inform Parse about this subclass
self.registerSubclass()
// 1
Post.imageCache = NSCacheSwift<String, UIImage>()
}
}
}
And here is my TableViewCell:
var post: Post? {
didSet {
postDisposable?.dispose()
likeDisposable?.dispose()
if let oldValue = oldValue where oldValue != post {
oldValue.image.value = nil
}
if let post = post {
postDisposable = post.image
.bindTo(postImageView.bnd_image)
likeDisposable = post.likes
.observe { (value: [PFUser]?) -> () in
if let value = value {
//self.likesLabel.text = self.stringFromUserList(value)
self.likeButton.selected = value.contains(PFUser.currentUser()!)
// self.likesIconImageView.hidden = (value.count == 0)
} else {
//self.likesLabel.text = ""
self.likeButton.selected = false
//self.likesIconImageView.hidden = true
}}}}}
Any help is really appreciated!
Solution 1
Add the header and tab as subviews to the content view. That way you get the scroll for free.
scrollView.contentView.addSubView(headerView)
scrollView.contentView.addSubView(tabView)
scrollView.contentView.addSubView(contentView)
let headerHight = 200
let tabHeight = 200
headerView.frame = CGRect(0, 0, UIScreen.mainScreen().bounds.width, headerHight)
tabView.frame = CGRect(0, headerHight, UIScreen.mainScreen().bounds.width, tabHeight)
contentView.frame = CGRect(0, headerHight + tabHeight, UIScreen.mainScreen().bounds.width, contentHeight)
Solution 2
Implement the UIScrollViewDelegate and move your header and tab on func scrollViewDidScroll(scrollView: UIScrollView)
Set up two constraints and outlets for the top position of the header and tab and change the constant value of the constraint to correspond to the scrollViews position
Solution 3
Implement everything in a tableView and set the header as the tableView.tableHeaderView. It might be better to also include the tabbar as a part of the header if it's supposed to follow the scroll of the header anyway.
Okay, there is the code example (very ugly UI)
override func viewDidLoad() {
super.viewDidLoad()
let screenWidth = view.frame.width
let screenHeight = view.frame.height
let subViewHeight = screenHeight / 3
let mainView = UIScrollView(frame: view.frame)
mainView.backgroundColor = UIColor.lightTextColor()
mainView.contentSize = CGSize(width: screenWidth, height: screenHeight + 100)
view = mainView
let firstView = UIView(frame: CGRect(x: 0, y: 0, width: screenWidth, height: subViewHeight))
firstView.backgroundColor = UIColor.redColor()
let secondView = UIView(frame: CGRect(x: 0, y: subViewHeight, width: screenWidth, height: subViewHeight))
secondView.backgroundColor = UIColor.blueColor()
let thirdView = UIScrollView(frame: CGRect(x: 0, y: subViewHeight * 2, width: screenWidth, height: subViewHeight + 100))
thirdView.contentSize = CGSize(width: screenWidth, height: screenHeight)
thirdView.backgroundColor = UIColor.greenColor()
view.addSubview(firstView)
view.addSubview(secondView)
view.addSubview(thirdView)
}