Setting a default child table view controller for segmented control - ios

I can use the segmented control to switch between child table view controllers within a container view, but it doesn't show the cells of the first table view controller when I enter the scene for some reason. I have my segment index defaulted at 0, so it shows the highlight on the first segment, but it does not correspondingly show the cells.
Here is the code for the segmentedControl and container view:
import UIKit
class BarMainViewController: UIViewController {
let barSegmentedControl: UISegmentedControl = {
let barItems = ["Newest", "Rewarding", "Saved"]
let segmentedControl = UISegmentedControl(items: barItems)
segmentedControl.selectedSegmentIndex = 0
segmentedControl.addTarget(self, action: #selector(selectionDidChange(_:)), for: .valueChanged)
return segmentedControl
}()
let containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(barSegmentedControl)
view.addSubview(containerView)
setDefaultVC()
barSegmentedControl.addUnderlineForSelectedSegment()
containerView.topAnchor.constraint(equalTo: barSegmentedControl.bottomAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -49).isActive = true
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
containerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
}
#objc func selectionDidChange(_ sender: UISegmentedControl) {
updateView()
barSegmentedControl.changeUnderlinePosition()
}
private func updateView() {
if barSegmentedControl.selectedSegmentIndex == 0 {
remove(asChildViewController: BarRewardingViewController())
remove(asChildViewController: BarSavedViewController())
add(asChildViewController: BarNewestViewController())
} else if barSegmentedControl.selectedSegmentIndex == 1 {
remove(asChildViewController: BarNewestViewController())
remove(asChildViewController: BarSavedViewController())
add(asChildViewController: BarRewardingViewController())
} else {
remove(asChildViewController: BarNewestViewController())
remove(asChildViewController: BarRewardingViewController())
add(asChildViewController: BarSavedViewController())
}
}
private func add(asChildViewController viewController: UITableViewController) {
// Add Child View Controller
addChildViewController(viewController)
// Add Child View as Subview
containerView.addSubview(viewController.view)
// Configure Child View
viewController.view.translatesAutoresizingMaskIntoConstraints = false
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
private func remove(asChildViewController viewController: UITableViewController) {
// Notify Child View Controller
viewController.willMove(toParentViewController: nil)
// Remove Child View From Superview
viewController.view.removeFromSuperview()
// Notify Child View Controller
viewController.removeFromParentViewController()
}
private func setDefaultVC() {
containerView.addSubview(BarNewestViewController().view)
}
}
And here is the code for the table view controller:
import UIKit
class BarNewestViewController: UITableViewController {
override func viewDidLoad() {
tableView.register(serviceCell.self, forCellReuseIdentifier: "service")
super.viewDidLoad()
view.backgroundColor = mainBlue
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "service", for: indexPath)
}
}
class serviceCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let nameLabel: UILabel = {
let label = UILabel()
label.text = "NAME"
label.font = UIFont(name: "Avnier-Heavy", size: 20)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setupViews() {
addSubview(nameLabel)
backgroundColor = lightBlue
nameLabel.topAnchor.constraint(equalTo: topAnchor).isActive = true
nameLabel.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
}
}

Because BarNewestViewController subclasses UITableViewController it automatically follows the UITableViewDataSource and UITableViewDelegate protocols e.g. tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int. It does NOT however tell the property tableView that comes with UITableViewController that it's delegate and dataSource is BarNewestViewController.
tableView.dataSource = self
tableView.delegate = self

Related

UITableView CustomCell Reuse (ImageView in CustomCell)

I'm pretty new to iOS dev and I have an issue with UITableViewCell.
I guess it is related to dequeuing reusable cell.
I added an UIImageView to my custom table view cell and also added a tap gesture to make like/unlike function (image changes from an empty heart(unlike) to a filled heart(like) as tapped and reverse). The problem is when I scroll down, some of the cells are automatically tapped. I found out why this is happening, but still don't know how to fix it appropriately.
Below are my codes,
ViewController
import UIKit
struct CellData {
var title: String
var done: Bool
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = models[indexPath.row]
guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as? TableViewCell else {
return UITableViewCell()
}
cell.textLabel?.text = model.title
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
tableView.reloadData()
}
}
TableViewCell
import UIKit
class TableViewCell: UITableViewCell {
let mainVC = ViewController()
static let identifier = "TableViewCell"
let likeImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(likeImage)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImage.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
likeImage.widthAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.heightAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
likeImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
if likeImage.image == UIImage(systemName: "heart.fill"){
likeImage.image = UIImage(systemName: "heart")
likeImage.tintColor = .darkGray
} else {
likeImage.image = UIImage(systemName: "heart.fill")
likeImage.tintColor = .systemRed
}
}
}
This gif shows how it works now.
enter image description here
I've tried to use "done" property in CellData structure to capture the status of the uiimageview but failed (didn't know how to use that in the correct way).
I would be so happy if anyone can help this!
You've already figured out that the problem is cell reuse.
When you dequeue a cell to be shown, you are already setting the cell label's text based on your data:
cell.textLabel?.text = model.title
you also need to tell the cell whether to show the empty or filled heart image.
And, when the user taps that image, your cell needs to tell the controller to update the .done property of your data model.
That can be done with either a protocol/delegate pattern or, more commonly (particularly with Swift), using a closure.
Here's a quick modification of the code you posted... the comments should give you a good idea of what's going on:
struct CellData {
var title: String
var done: Bool
}
class ShinViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(ShinTableViewCell.self, forCellReuseIdentifier: ShinTableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ShinTableViewCell.identifier, for: indexPath) as! ShinTableViewCell
let model = models[indexPath.row]
cell.myLabel.text = model.title
// set the "heart" to true/false
cell.isLiked = model.done
// closure
cell.callback = { [weak self] theCell, isLiked in
guard let self = self,
let pth = self.tableView.indexPath(for: theCell)
else { return }
// update our data
self.models[pth.row].done = isLiked
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
class ShinTableViewCell: UITableViewCell {
// we'll use this closure to communicate with the controller
var callback: ((UITableViewCell, Bool) -> ())?
static let identifier = "TableViewCell"
let likeImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let myLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// we'll load the heart images once in init
// instead of loading them every time they change
var likeIMG: UIImage!
var unlikeIMG: UIImage!
var isLiked: Bool = false {
didSet {
// update the image in the image view
likeImageView.image = isLiked ? likeIMG : unlikeIMG
// update the tint
likeImageView.tintColor = isLiked ? .systemRed : .darkGray
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// make sure we load the heart images
guard let img1 = UIImage(systemName: "heart"),
let img2 = UIImage(systemName: "heart.fill")
else {
fatalError("Could not load the heart images!!!")
}
unlikeIMG = img1
likeIMG = img2
// add label and image view
contentView.addSubview(myLabel)
contentView.addSubview(likeImageView)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImageView.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
// let's use the "built-in" margins guide
let g = contentView.layoutMarginsGuide
// image view bottom constraint
let bottomConstraint = likeImageView.bottomAnchor.constraint(equalTo: g.bottomAnchor)
// this will avoid auto-layout complaints
bottomConstraint.priority = .required - 1
NSLayoutConstraint.activate([
// constrain label leading
myLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
// center the label vertically
myLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// constrain image view trailing
likeImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// constrain image view to 30 x 30
likeImageView.widthAnchor.constraint(equalToConstant: 30),
likeImageView.heightAnchor.constraint(equalTo: likeImageView.widthAnchor),
// constrain image view top
likeImageView.topAnchor.constraint(equalTo: g.topAnchor),
// activate image view bottom constraint
bottomConstraint,
])
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
// toggle isLiked (true/false)
isLiked.toggle()
// inform the controller, so it can update the data
callback?(self, isLiked)
}
}

Swift UISwitch Button with Label

I am receiving data from API . The data field is displayed with two Label control, Id and status . Here is the screenshot .
I have another view where I created switch button and label programatically. I want to change the status to false when user turn on to switch button but it now updating the value . Here is the code and function i defined .
class DetailsViewController : UIViewController{
#IBOutlet private weak var tableView: UITableView!
var changeStatus: ((Bool, String) -> Void)?
var identifier = ""
private let switchControl: UISwitch = {
let switchControl = UISwitch()
switchControl.translatesAutoresizingMaskIntoConstraints = false
switchControl.isOn = false
return switchControl
}()
#IBOutlet weak var customSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.addSubview(switchControl)
switchControl.addTarget(self, action: #selector(changeSwitchControl), for: .valueChanged)
let safeArea = view.safeAreaLayoutGuide
switchControl.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
switchControl.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
setUpUI()
}
#objc
private func changeSwitchControl() {
changeStatus?(switchControl.isOn, identifier)
}
}
Here is code in View Controller .
class ViewController: UIViewController {
// ! Mark is means it not null the value will in story board
// use lazy property to tell compiler instance value of the datasource self class and execuate the controller code to set the value of tableview datasource
private let viewModel = ViewModel()
private var subcriber = Set<AnyCancellable>()
private var storiesTrue = [String]()
#Published private(set) var stories = [Rover]()
private var datasourceStories = [Rover]()
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false// adding constrains
tableview.dataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(StoryCell.self, forCellReuseIdentifier: StoryCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
setUpBinding()
tableView.dataSource = self
tableView.delegate = self
self.tableView.rowHeight = 44;
// display the second view controller using push methods
/*let detail = DetailsViewController()
detail.name "Mohammad"
navigationController?.pushViewController(detail, animated: true)*/
}
private func setUpUI() {
view.backgroundColor = .white
view.addSubview(tableView)// adding hierracy key like adding into story board
// creating constrains with safe area
let safeArea = view.safeAreaLayoutGuide
tableView.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
}
private func setUpBinding(){
viewModel
.$rovers
.receive(on : RunLoop.main)
.sink {[weak self]_ in
self?.tableView.reloadData()
}
.store(in: &subcriber)
viewModel.getStories()
}
private func getStatus(by identifier: String) -> Bool {
return storiesTrue.contains(identifier)
}
}
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.rovers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: StoryCell.identifier , for: indexPath) as? StoryCell
else{ return UITableViewCell()}
let row = indexPath.row
let Id = viewModel.getId(by: row)
let title = viewModel.getTitle(by: row)
let identifier = viewModel.getIdentifier(by: indexPath.row)
let status = getStatus(by: identifier) ? "active" : "false"
cell.configureCell(Id: Id,title: title,statusString: status)
return cell
}
}
extension ViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getStories()
}
}
extension ViewController : UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let second = DetailsViewController()
navigationController?.pushViewController(second, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 44;
}
}
Here is the view controller to defined the UI properties .
class StoryCell: UITableViewCell {
static let identifier = "StoryCell"
private lazy var storyIdLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
public lazy var storyTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
private lazy var statusStory: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(Id: Int,title: String,statusString: String) {
storyIdLabel.text = "Id: \(String(Id))"
storyTitleLabel.text = "Status :\(title)"
statusStory.text = "Status:\(statusString)"
}
/* func configureCell(Id: Int) {
storyIdLabel.text = "Id: \(String(Id))"
}*/
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
private func setUpUI() {
contentView.addSubview(storyTitleLabel)
// constraints
let safeArea = contentView.safeAreaLayoutGuide
storyTitleLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
storyTitleLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
storyTitleLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
contentView.addSubview(storyIdLabel)
let safeArea1 = contentView.safeAreaLayoutGuide
storyIdLabel.topAnchor.constraint(equalTo: storyTitleLabel.topAnchor).constant = 5
storyIdLabel.bottomAnchor.constraint(equalTo: safeArea1.bottomAnchor).isActive = true
storyIdLabel.leadingAnchor.constraint(equalTo: safeArea1.leadingAnchor).isActive = true
storyIdLabel.trailingAnchor.constraint(equalTo: safeArea1.trailingAnchor).isActive = true
/* contentView.addSubview(statusStory)
let safeArea2 = contentView.safeAreaLayoutGuide
statusStory.topAnchor.constraint(equalTo: statusStory.topAnchor).isActive = true
statusStory.bottomAnchor.constraint(equalTo: safeArea2.bottomAnchor).isActive = true
statusStory.leadingAnchor.constraint(equalTo: safeArea2.leadingAnchor).isActive = true
statusStory.trailingAnchor.constraint(equalTo: safeArea2.trailingAnchor).isActive = true*/
}
}
You have to use tableView.reloadData() after updating info that you want to show. Without running this function, data in table view won't be updated.

UITableViewCell incorrect dynamic height calculation with SwiftUI view inside on iOS 15

I am trying to use SwiftUI view inside UITableViewCell that is working fine until iOS 15. On iOS 15 when I scroll table view and once initial visible cells go off screen and new cells being reused those are having extra height added.
Following is the source code.
import UIKit
import SwiftUI
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
tableView?.register(TableViewCell.self, forCellReuseIdentifier: "cell")
tableView?.rowHeight = UITableView.automaticDimension
tableView?.estimatedRowHeight = 100
tableView?.separatorColor = .white
tableView?.dataSource = self
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell
return cell ?? UITableViewCell()
}
}
class TableViewCell: UITableViewCell {
var stackView: UIStackView = {
let stack = UIStackView(frame: .zero)
stack.axis = .vertical
return stack
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView() {
contentView.clipsToBounds = true
contentView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
let hosting = UIHostingController(rootView: TestView())
stackView.addArrangedSubview(hosting.view)
hosting.view.backgroundColor = .red
}
}
struct TestView: View {
var body: some View {
Rectangle()
.fill(Color.green)
.frame(height: 100)
}
}
Any help will be highly appreciated. Thanks

How to tell if UITableView in custom UISearchBar is touched?

I am trying to create a custom UISearchBar that is placed as the titleView of a navigationController. Using the following code, the suggestionTableView of suggestions appears perfectly; however, It does not recognize any taps. In fact, it is like the suggestionTableView isn't even there because my taps are being sent to another view under the suggestion suggestionTableView. I was told that I could use hitTest(...) to catch these touches, but I don't know how I would implement this in my SuggestionSearchBar or in my ViewController. How can I send these touches to the suggestionTableView?
SuggestionSearchBar
class SuggestionSearchBar: UISearchBar, UISearchBarDelegate {
var suggestionTableView = UITableView(frame: .zero)
let allPossibilities: [String]!
var possibilities = [String]()
//let del: UISearchBarDelegate!
init(del: UISearchBarDelegate, dropDownPossibilities: [String]) {
self.allPossibilities = dropDownPossibilities
super.init(frame: .zero)
isUserInteractionEnabled = true
delegate = del
searchTextField.addTarget(self, action: #selector(searchBar(_:)), for: .editingChanged)
searchTextField.addTarget(self, action: #selector(searchBarCancelButtonClicked(_:)), for: .editingDidEnd)
sizeToFit()
addTableView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addTableView() {
let cellHeight = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "").frame.height
suggestionTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
suggestionTableView.backgroundColor = UIColor.clear
//suggestionTableView.separatorStyle = .none
suggestionTableView.tableFooterView = UIView()
addSubview(suggestionTableView)
suggestionTableView.delegate = self
suggestionTableView.dataSource = self
suggestionTableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
suggestionTableView.topAnchor.constraint(equalTo: bottomAnchor),
suggestionTableView.rightAnchor.constraint(equalTo: rightAnchor),
suggestionTableView.leftAnchor.constraint(equalTo: leftAnchor),
suggestionTableView.heightAnchor.constraint(equalToConstant: cellHeight*6),
])
hideSuggestions()
}
func showSuggestions() {
let sv = suggestionTableView.superview
sv?.clipsToBounds = false
suggestionTableView.isHidden = false
}
func hideSuggestions() {
suggestionTableView.isHidden = true
}
#objc func searchBar(_ searchBar: UISearchBar) {
print(searchBar.text?.uppercased() ?? "")
showSuggestions()
possibilities = allPossibilities.filter {$0.contains(searchBar.text?.uppercased() ?? "")}
print(possibilities.count)
suggestionTableView.reloadData()
if searchBar.text == "" || possibilities.count == 0 {
hideSuggestions()
}
}
#objc func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
hideSuggestions()
}
}
extension SuggestionSearchBar: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return possibilities.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = suggestionTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.backgroundColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 0.75)
if traitCollection.userInterfaceStyle == .light {
cell.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.75)
}
cell.textLabel?.text = possibilities[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//add method that fills in and searches based on the text in that indexpath.row
print("selected")
}
}
ViewController
import UIKit
class ViewController: UIViewController {
lazy var searchBar = SuggestionSearchBar(del: self, dropDownPossibilities: ["red","green","blue","yellow"])
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
}
func setUpUI() {
setUpSearchBar()
}
}
extension ViewController: UISearchBarDelegate {
func setUpSearchBar() {
searchBar.searchBarStyle = UISearchBar.Style.prominent
searchBar.placeholder = "Search"
searchBar.sizeToFit()
searchBar.isTranslucent = false
searchBar.backgroundImage = UIImage()
searchBar.delegate = self
navigationItem.titleView = searchBar
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
print(searchBar.text!)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.endEditing(true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
}
}
Reviewing your provided code, I can get the UI to work properly and even get the UITableViewDelegate callbacks inside SuggestionSearchBar.
Here are the changes
Suggestion Search Bar
class SuggestionSearchBar: UISearchBar, UISearchBarDelegate {
var suggestionTableView = UITableView(frame: .zero)
let allPossibilities: [String]!
var possibilities = [String]()
var fromController: UIViewController?
//let del: UISearchBarDelegate!
init(del: UISearchBarDelegate, dropDownPossibilities: [String], fromController: UIViewController) {
self.fromController = fromController
self.allPossibilities = dropDownPossibilities
super.init(frame: .zero)
isUserInteractionEnabled = true
delegate = del
searchTextField.addTarget(self, action: #selector(searchBar(_:)), for: .editingChanged)
searchTextField.addTarget(self, action: #selector(searchBarCancelButtonClicked(_:)), for: .editingDidEnd)
sizeToFit()
addTableView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addTableView() {
guard let view = fromController?.view else {return}
let cellHeight = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "").frame.height
suggestionTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
suggestionTableView.backgroundColor = UIColor.clear
//suggestionTableView.separatorStyle = .none
suggestionTableView.tableFooterView = UIView()
view.addSubview(suggestionTableView)
// addSubview(suggestionTableViewse
suggestionTableView.delegate = self
suggestionTableView.dataSource = self
suggestionTableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
suggestionTableView.topAnchor.constraint(equalTo: view.topAnchor),
suggestionTableView.rightAnchor.constraint(equalTo: view.rightAnchor),
suggestionTableView.leftAnchor.constraint(equalTo: view.leftAnchor),
suggestionTableView.heightAnchor.constraint(equalToConstant: cellHeight*6),
])
hideSuggestions()
}
func showSuggestions() {
let sv = suggestionTableView.superview
sv?.clipsToBounds = false
suggestionTableView.isHidden = false
}
func hideSuggestions() {
suggestionTableView.isHidden = true
}
#objc func searchBar(_ searchBar: UISearchBar) {
print(searchBar.text?.uppercased() ?? "")
showSuggestions()
possibilities = allPossibilities.filter {$0.contains(searchBar.text?.lowercased() ?? "")}
print(possibilities.count)
suggestionTableView.reloadData()
if searchBar.text == "" || possibilities.count == 0 {
hideSuggestions()
}
}
#objc func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
hideSuggestions()
}
}
ViewController
class ViewController: UIViewController {
lazy var searchBar = SuggestionSearchBar(del: self, dropDownPossibilities: ["red","green","blue","yellow"], fromController: self)
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
}
func setUpUI() {
setUpSearchBar()
}
}
To summarise the changes, the code above tried to add suggestionTableView
to the SearchBarView which is not possible so I initialized SearchBarView with the reference to the parent ViewController which is stored as
var fromController: UIViewController?
This property is later used in addTableView()
private func addTableView() {
guard let view = fromController?.view else {return}
let cellHeight = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "").frame.height
suggestionTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
suggestionTableView.backgroundColor = UIColor.clear
//suggestionTableView.separatorStyle = .none
suggestionTableView.tableFooterView = UIView()
view.addSubview(suggestionTableView)
// addSubview(suggestionTableViewse
suggestionTableView.delegate = self
suggestionTableView.dataSource = self
suggestionTableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
suggestionTableView.topAnchor.constraint(equalTo: view.topAnchor),
suggestionTableView.rightAnchor.constraint(equalTo: view.rightAnchor),
suggestionTableView.leftAnchor.constraint(equalTo: view.leftAnchor),
suggestionTableView.heightAnchor.constraint(equalToConstant: cellHeight*6),
])
hideSuggestions()
}
There is another small change
possibilities = allPossibilities.filter {$0.contains(searchBar.text?.lowercased() ?? "")}
in #objc func searchBar(_ searchBar: UISearchBar) {
Result
As long as you are adding the UITableView as a subview to the SearchBar or UINavigationBar, you will keep finding these touch issues.
A possible way to handle this would be have an empty container UIView instance at the call site (ViewController in your case) and ask SuggestionsSearchBar to add it's tableView inside that container.
SuggestionSearchBar(
del: self,
suggestionsListContainer: <UIStackView_Inside_ViewController>,
dropDownPossibilities: ["red","green","blue","yellow"]
)
SuggestionsSearchBar will still manage everything about the tableView's dataSource, delegate, it's visibility etc. It just needs a view that can hold it's tableView from the call site.
UPDATE
I'm highlighting only the relevant parts that need to change - everything else remains the same.
class SuggestionSearchBar: UISearchBar, UISearchBarDelegate {
init(del: UISearchBarDelegate, suggestionsListContainer: UIStackView, dropDownPossibilities: [String]) {
//// All the current setUp
addTableView(in: suggestionsListContainer)
}
private func addTableView(in container: UIStackView) {
//// All the current setUp
container.addArrangedSubview(suggestionTableView)
NSLayoutConstraint.activate([
suggestionTableView.heightAnchor.constraint(equalToConstant: cellHeight*6),
/// We need to assign only height here
/// top, leading, trailing will be driven by container at call site
])
}
}
class ViewController: UIViewController {
lazy var suggestionsListContainer: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
lazy var searchBar = SuggestionSearchBar(
del: self,
suggestionsListContainer: suggestionsListContainer,
dropDownPossibilities: ["red","green","blue","yellow"]
)
func setUpUI() {
setUpSearchBar()
setUpSuggestionsListContainer()
}
func setUpSuggestionsListContainer() {
self.view.addSubview(suggestionsListContainer)
NSLayoutConstraint.activate([
suggestionsListContainer.topAnchor.constraint(equalTo: self.view.topAnchor),
suggestionsListContainer.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
suggestionsListContainer.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
/// Height is not needed as it will be driven by tableView's height
])
}
}

How can implement two vertical button in swipe to delete in swift 5

I am trying to implement swipe to delete feature with two options, one is to delete and another one is to edit. The things I want is these options should be vertical rather than horizontal.
Thanks in advance for support.
You can easily achieve this swipe to reveal option feature using Custom TablViewCell
Design a view with two buttons and add a swipe gesture to the view to reveal the vertically aligned buttons
Anyway, I think you would rather use default method editActionsForRowAt for similar cases. If not I hope this code will help you.
class TableViewController: UIViewController {
let tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(CustomCell.self,
forCellReuseIdentifier: CustomCell.identifier)
return tableView
}()
var selectedCell: CustomCell?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(tableView)
setupConstraints()
tableView.dataSource = self
tableView.delegate = self
tableView.reloadData()
}
func setupConstraints() {
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
extension TableViewController: UITableViewDelegate & UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: CustomCell.identifier, for: indexPath) as? CustomCell else {
return UITableViewCell()
}
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
44
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
selectedCell?.setInitialState()
}
}
extension TableViewController: CustomCellDelegate {
func didUpdateState(customCell: CustomCell?) {
if customCell != selectedCell {
selectedCell?.setInitialState()
}
selectedCell = customCell
}
}
protocol CustomCellDelegate: class {
func didUpdateState(customCell: CustomCell?)
}
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
static let identifier = "Cell"
private let customViewWidth: CGFloat = 100
private let customView: CustomView = {
let view = CustomView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .yellow
return view
}()
private let fakeContentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
enum CustomCellState {
case hiddenCustomView
case showedCustomView
}
private var trailingConstraint = NSLayoutConstraint()
private var cellState: CustomCellState = .hiddenCustomView
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .yellow
let panGesture = UIPanGestureRecognizer(target: self, action:(#selector(handleGesture(_:))))
fakeContentView.addGestureRecognizer(panGesture)
contentView.addSubview(fakeContentView)
fakeContentView.addSubview(customView)
setConstraints()
updateCellState()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setInitialState() {
self.trailingConstraint.constant = 0
UIView.animate(withDuration: 0.5, animations: {
self.contentView.layoutIfNeeded()
})
}
#objc private func handleGesture(_ recognizer: UIPanGestureRecognizer) {
let location = recognizer.location(in: fakeContentView)
let dx = frame.width - location.x
updateFrame(deltaX: dx)
if recognizer.state == .ended {
cellState = dx > customViewWidth / 2 ? .showedCustomView : .hiddenCustomView
updateCellState()
delegate?.didUpdateState(customCell: self)
}
}
private func updateFrame(deltaX: CGFloat) {
guard abs(deltaX) <= customViewWidth else {
return
}
trailingConstraint.constant = -deltaX
contentView.layoutIfNeeded()
}
private func updateCellState() {
let dx: CGFloat = cellState == .hiddenCustomView ? 0 : customViewWidth
self.trailingConstraint.constant = -dx
UIView.animate(withDuration: 0.5, animations: {
self.contentView.layoutIfNeeded()
}, completion: nil)
}
private func setConstraints() {
trailingConstraint = fakeContentView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
trailingConstraint.isActive = true
NSLayoutConstraint.activate([
fakeContentView.topAnchor.constraint(equalTo: contentView.topAnchor),
fakeContentView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
fakeContentView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
customView.topAnchor.constraint(equalTo: fakeContentView.topAnchor),
customView.bottomAnchor.constraint(equalTo: fakeContentView.bottomAnchor),
customView.leadingAnchor.constraint(equalTo: fakeContentView.trailingAnchor),
customView.widthAnchor.constraint(equalToConstant: customViewWidth)
])
}
}
class CustomView: UIView {
private let button1: UIButton = {
let button = UIButton(type: .custom)
button.backgroundColor = .blue
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private let button2: UIButton = {
let button = UIButton(type: .custom)
button.backgroundColor = .black
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .gray
addSubview(button1)
addSubview(button2)
setConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setConstraints() {
NSLayoutConstraint.activate([
button1.topAnchor.constraint(equalTo: topAnchor),
button1.leadingAnchor.constraint(equalTo: leadingAnchor),
button1.trailingAnchor.constraint(equalTo: trailingAnchor),
button1.bottomAnchor.constraint(equalTo: centerYAnchor),
button2.bottomAnchor.constraint(equalTo: bottomAnchor),
button2.leadingAnchor.constraint(equalTo: leadingAnchor),
button2.trailingAnchor.constraint(equalTo: trailingAnchor),
button2.topAnchor.constraint(equalTo: button1.bottomAnchor)
])
}
}

Resources