Swift: How to update data in Container View without storyboard - ios

My project is created programmatically without using storyboard. And it is like Apple Music's miniPlayer, when clicking a row in tableView, will update the data of miniPlayer(which is in containerView).
I see some examples with storyboard and segue like below code: call child viewController's method in parent viewController to update data by using protocol & delegate.
But I don't use storyboard, so what is the alternative code to prepare()?
protocol ContentDelegate {
func updateContent(id: Int)
}
class ParentViewController: UIViewController {
var delegate: ContentDelegate?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "containerController") {
let containerVC = segue.destination as! ChildContainerViewController
self.delegate = containerVC
}
}
}
class ChildContainerViewController: UIViewController, ContentDelegate {
func updateContent(id: Int) {
// your code
}
}
My Code: add container view in the root view controller(UITabViewController).
class ViewController: UITabBarController {
// mini player
var miniPlayer: MiniPlayerViewController?
// container view
var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// set tabBar and other stuff
...
configureContainer()
}
func configureContainer() {
// add container
containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(containerView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
containerView.bottomAnchor.constraint(equalTo: tabBar.topAnchor),
containerView.heightAnchor.constraint(equalToConstant: 64.0)
])
// add child view controller view to container
miniPlayer = MiniPlayerViewController()
guard let miniPlayer = miniPlayer else { return }
addChild(miniPlayer)
miniPlayer.view.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(miniPlayer.view)
// Create and activate the constraints for the child’s view.
guard let miniPlayerView = miniPlayer.view else { return }
NSLayoutConstraint.activate([
miniPlayerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
miniPlayerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
miniPlayerView.topAnchor.constraint(equalTo: containerView.topAnchor),
miniPlayerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])
miniPlayer.didMove(toParent: self)
}
}
I want to trigger the update when clicking the row in parentView.
protocol ContentDelegate {
func configure(songs: [Song]?, at index: Int)
}
class SongsListViewController: UIViewController {
private var tableView: UITableView!
var delegate: ContentDelegate?
// MARK: - data source
var songs = [Song]()
. . .
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let index = indexPath.row
let vc = MiniPlayerViewController()
self.delegate = vc
self.delegate?.configure(songs: songs, at: index)
// present(vc, animated: true)
}
The update method in child view.
extension MiniPlayerViewController {
func configure(songs: [Song]?, at index: Int) {
if let songs = songs {
let song = songs[index]
songTitle.text = song.title
thumbImage.image = song.artwork?.image
} else {
// placeholder fake info
songTitle.text = "你在终点等我"
thumbImage.image = UIImage(named: "Wang Fei")
}
}
}

There is more than one approach to this...
First approach - no custom delegate:
Use the subclassed UITabBarController as an "intermediary". Give it a func such as:
func configure(songs: [Song]?, at index: Int) -> Void {
miniPlayer.configure(songs: songs, at: index)
}
then, in your "Select Song" view controller (one of the tabs):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let tbc = self.tabBarController as? CustomTabBarController else {
return
}
let index = indexPath.row
tbc.configure(songs: songs, at: index)
}
Second approach - using a custom delegate:
protocol ContentDelegate {
func configure(songs: [Song]?, at index: Int)
}
Make sure your "mini player" controller conforms to the delegate:
class MiniPlayerViewController: UIViewController, ContentDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// add UI elements, any other setup code
}
}
extension MiniPlayerViewController {
func configure(songs: [Song]?, at index: Int) {
if let songs = songs {
let song = songs[index % songs.count]
songTitle.text = song.title
thumbImage.image = song.artwork
} else {
// placeholder fake info
songTitle.text = "你在终点等我"
thumbImage.image = UIImage(named: "Wang Fei")
}
}
}
Give your "Select Song" view controller (and any other of the tab controllers) a delegate var:
class SelectSongViewController: UIViewController {
var delegate: ContentDelegate?
// everything else
}
then, in your subclassed UITabBarController:
override func viewDidLoad() {
super.viewDidLoad()
configureContainer()
if let vc = viewControllers?.first as? SelectSongViewController {
vc.delegate = miniPlayer
}
}
now your "Select Song" view controller can call the delegate func:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let tbc = self.tabBarController as? CustomTabBarController else {
return
}
let index = indexPath.row
delegate?.configure(songs: songs, at: index)
}

Related

Reusable UITableViewController

I want to avoid duplicate rewriting same code and create reusable UITableViewController.
I have ExerciseViewController with 3 buttons. Each button push a UITableViewController on the navigation stack. There are three UITableViewControllers: 1) CategoryUITableVC, 2) EquipmentUITableVC, 3) MusclesUITableVC.
All of these three view controllers have almost exactly same layout - cells with labels and accessory buttons. The only difference is that first view controller has got image next to title. Is it worth doing one reusable VC and instantiate it 3 times or maybe better solution create 3 separated VC (but It will be just rewriting almost same code).
I use coordinator pattern.
class ExerciseCoordinator: NSObject, Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
...
//unnecessary code to show
...
// *HERE I INSTANTIATE VIEW CONTROLLERS, I PRESENT THEM MODALLY BUT I WANT TO HAVE NAVIGATION BAR, SO I NEED TO CREATE NEW NAVIGATION CONTROLLERS*
lazy var equipmentVC: ReusableTableViewController = {
let vc = AppStoryboard.Main.instantiate(ReusableTableViewController.self)
vc.delegate = self
return vc
}()
lazy var equipmentNavController: UINavigationController = {
let navController = UINavigationController(rootViewController: equipmentVC)
navController.navigationItem.largeTitleDisplayMode = .always
return navController
}()
lazy var categoryVC: ReusableTableViewController = {
let vc = AppStoryboard.Main.instantiate(ReusableTableViewController.self)
vc.delegate = self
return vc
}()
lazy var categoryNavController: UINavigationController = {
let navController = UINavigationController(rootViewController: categoryVC)
navController.navigationItem.largeTitleDisplayMode = .always
return navController
}()
lazy var muscleVC: ReusableTableViewController = {
let vc = AppStoryboard.Main.instantiate(ReusableTableViewController.self)
vc.delegate = self
return vc
}()
lazy var muscleNavController: UINavigationController = {
let navController = UINavigationController(rootViewController: muscleVC)
navController.navigationItem.largeTitleDisplayMode = .always
return navController
}()
}
extension ExerciseCoordinator: CustomExerciseDelegate {
func selectCategory() {
navigationController.viewControllers.last?.present(categoryNavController, animated: true, completion: nil)
categoryVC.dataType = .category
}
func selectEquipment() {
navigationController.viewControllers.last?.present(equipmentNavController, animated: true, completion: nil)
equipmentVC.dataType = .equipment
}
func selectMuscles() {
navigationController.viewControllers.last?.present(muscleNavController, animated: true, completion: nil)
muscleVC.dataType = .muscle
}
}
I assign data type to know from where it comes from (CategoryVC/EquipmentVC/MuscleVC) when I will dismiss UITableVC.
Here it is my reusable UITableViewController:
import UIKit
import RealmSwift
class ExerciseCategoryTableViewController: UITableViewController {
var delegate: ExerciseSelectionCriteriaDelegate?
//I use it delegate to send data back after dismiss view
var dataType: DataType?
var data = [String]() {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData() }}
}
override func viewDidLoad() {
super.viewDidLoad()
getData()
}
func getData() {
if dataType == .category {
let allCategories = RealmService.shared.realm.objects(Category.self)
data = allCategories.compactMap({$0.category})
} else if dataType == .equipment {
let allEquipment = RealmService.shared.realm.objects(Equipment.self)
data = allEquipment.compactMap({$0.equipment})
} else {
let allMuscles = RealmService.shared.realm.objects(Muscles.self)
data = allMuscles.compactMap({$0.muscles})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// below is my own shortcut for dequeue cell, it works
let cell: ExerciseSelectionTableViewCell = tableView.dequeueResuableCell(for: indexPath)
cell.category.text = data[indexPath.row]
if let image = UIImage(named: "\(data[indexPath.row].lowercased())") {
cell.categoryImage.image = image
cell.categoryImage.isHidden = false
} else {
cell.categoryImage.isHidden = true
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
#IBAction func closeViewController(_ sender: UIBarButtonItem) {
closeViewController()
}
#IBAction func saveSelectedCategories(_ sender: UIBarButtonItem) {
saveSelectedData()
}
func saveSelectedData() {
let selectedIndexes = tableView.indexPathsForSelectedRows
if let selectedData = selectedIndexes?.compactMap({data[$0.row]}) {
dismiss(animated: true) {
self.delegate?.selectedFields(for: selectedData, dataType: self.dataType)
}
} else {
dismiss(animated: true) {
self.delegate?.selectedFields(for: nil, dataType: self.dataType)
}
}
}
func closeViewController() {
guard let _ = tableView.indexPathsForSelectedRows else { return dismiss(animated: true, completion: nil)}
Alert.showAlertWithCompletion(on: self, with: "TEXT", message: "TEXT") { _ in
self.dismiss(animated: true, completion: nil)
}
}
}
I will be thankful if you tell me if this approach is correct or maybe better solution are separated view controllers or create protocol with default implementation?

duplicate data showing in UITableView after UIViewController being dismissed

Problems :
UITableview populate duplicate data even after removing the array when it dismiss
Expected output:
UITableview populate data based on itenary items in array
Actual output:
UITableView populates the correct amount output when the user selects the first location in DiscoverVC but when the user selects another location, the tableview append the itenary data that the user-selected previouly.
Summary:
I have 3VC in my project, first vc (DiscoverVC), will call api to populate data in UICollectionView, I implement UICollectionView Delegate to move to another screen with segue, in prepare segue I pass data from the first vc to second vc (ItenaryVC), in second vc i have 2 view inside it. One normal vc and the second is floating panel (ItenaryFP). When second vc loads up it will make API calls base on Location object that had been pass from first vc and pass the data to the third vc which is floating panel (ItenaryFP) through delegate in ItenaryVC.
PS; I use custom cell for the tableview and have, I also already try to remove the array from viewWillAppear and viewDidDissapear but it's still not working
GIF of how the issues occurs
Here is a summary of my code
DiscoverVC.swift
class DiscoverVC : UIViewController {
//MARK:- IBOutlets
#IBOutlet weak var collectionView: UICollectionView!
private var locationResult = [Location]()
private var selectedAtRow : Int!
//MARK:- Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
renderView()
getLocations()
}
private func renderView() {
collectionView.register(UINib(nibName: R.nib.discoverCell.name, bundle: nil), forCellWithReuseIdentifier: R.reuseIdentifier.discoverCell.identifier)
collectionView.delegate = self
collectionView.dataSource = self
}
private func getLocations(location : String = "locations") {
NetworkManager.shared.getLocations(for: location) { [weak self] location in
switch location {
case .success(let locations):
self?.updateDiscoverUI(with: locations)
case .failure(let error):
print(error.rawValue)
}
}
}
private func updateDiscoverUI(with locations : [Location]) {
DispatchQueue.main.async { [weak self] in
self?.locationResult.append(contentsOf: locations)
self?.collectionView.reloadData()
}
}
}
//MARK:- Delegate
extension DiscoverVC : UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedAtRow = indexPath.row
self.performSegue(withIdentifier: R.segue.discoverVC.goToDetails, sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destinationVC = segue.destination as? ItenaryVC else { return}
// Passing location object to ItenaryVC
destinationVC.locationName = locationResult[selectedAtRow]
destinationVC.imageURL = locationResult[selectedAtRow].image
// Remove tab bar when push to other vc
destinationVC.hidesBottomBarWhenPushed = true
}
}
ItenaryVC.swift
protocol ItenaryVCDelegate : AnyObject {
func didSendItenaryData(_ itenaryVC : ItenaryVC, with itenary : [[Days]])
func didSendLocationData(_ itenaryVC : ItenaryVC, with location : Location)
}
class ItenaryVC: UIViewController {
#IBOutlet weak var backgroundImage: UIImageView!
var fpc : FloatingPanelController!
var imageURL : URL?
var locationName: Location?
weak var delegate : ItenaryVCDelegate?
//MARK:- Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setupCard()
setupView()
}
override func viewWillAppear(_ animated: Bool) {
// Call API for data
getItenaries(at: locationName!.itenaryName)
print("ItenaryVC Appear")
}
override func viewWillDisappear(_ animated: Bool) {
locationName = nil
}
}
//MARK:- Network Request
extension ItenaryVC {
func getItenaries(at itenaries : String = "Melaka"){
print("itenaries : \(itenaries)")
NetworkManager.shared.getItenaries(for: itenaries) { [weak self] itenary in
switch itenary {
case .success(let itenary):
// print(itenary)
DispatchQueue.main.async {
// Passing data to itenaryFP
self?.delegate?.didSendItenaryData(self! , with: itenary)
}
print(itenaries.count)
case .failure(let error):
print(error.rawValue)
}
}
}
}
//MARK:- Private methods
extension ItenaryVC {
private func setupView() {
backgroundImage.downloaded(from: imageURL!)
backgroundImage.contentMode = .scaleAspectFill
// Passing data to itenaryFP
delegate?.didSendLocationData(self, with: locationName!)
}
private func setupCard() {
guard let itenaryFlotingPanelVC = storyboard?.instantiateViewController(identifier: "itenaryPanel") as? ItenaryFP else { return}
// Initliase delegate to Floating Panel, create strong reference to Panel
self.delegate = itenaryFlotingPanelVC
fpc = FloatingPanelController()
fpc.set(contentViewController: itenaryFlotingPanelVC)
fpc.addPanel(toParent: self)
fpc.delegate = self
fpc.layout = self
}
}
ItenaryFP.swift
class ItenaryFP: UIViewController{
var itenaries = [[Days]]()
var location : Location?
//MARK:- : Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
print("ItrenaryFP viewDidLoad, itenaries : \(itenaries.count), location : \(location)")
renderView()
}
override func viewWillAppear(_ animated: Bool) {
itenaries.removeAll()
itenaryTableView.reloadData()
}
override func viewWillDisappear(_ animated: Bool) {
DispatchQueue.main.async {
self.itenaries.removeAll()
print("ItenarFP dissapear, itenaries :\(self.itenaries.count), location : \(self.location)")
self.location = nil
self.itenaryTableView.reloadData()
}
}
private func renderView() {
itenaryTableView.register(UINib(nibName: R.nib.itenaryCell.name, bundle: nil), forCellReuseIdentifier: R.nib.itenaryCell.identifier)
itenaryTableView.dataSource = self
itenaryTableView.delegate = self
locDescHC.constant = locDesc.contentSize.height
}
}
//MARK:- Data source
extension ItenaryFP : UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return itenaries.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itenaries[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : ItenaryCell = itenaryTableView.dequeueReusableCell(withIdentifier: R.nib.itenaryCell.identifier, for: indexPath) as! ItenaryCell
let listOfItenaries = itenaries[indexPath.section][indexPath.row]
cell.cellContent(for: listOfItenaries)
return cell
}
}
//MARK:- ItenaryVC Delegate
extension ItenaryFP : ItenaryVCDelegate {
func didSendLocationData(_ itenaryVC: ItenaryVC, with location: Location) {
DispatchQueue.main.async {
self.locationLabel.text = location.locationName
self.locDesc.text = location.description
self.sloganLabel.text = location.slogan
}
}
func didSendItenaryData(_ itenaryVC: ItenaryVC, with itenary: [[Days]]) {
DispatchQueue.main.async {
self.itenaries.append(contentsOf: itenary)
self.itenaryTableView.reloadData()
print("itenary \(self.itenaries.count)")
}
}
}
Use:
self.itenaries = itenary
Instead of:
self.itenaries.append(contentsOf: itenary)

How to handle tableview cell clicks and segues when delegate and datasource class is separate?

I tried to separate TableViewDelegate and TableViewDataSource to a separate class from ViewController and I'm facing a couple of problems now.
First problem:
App loads the tableview with all content but when I click on it or try to scroll all data disappears.
Second problem:
On click cell should link to another view where is more content displayed. I push data to this view using function. But when I separated the delegate and datasource to other class it doesnt work.
prepare(for segue: UIStoryboardSegue, sender: Any?)
Here is my code for view controller:
import UIKit
import Foundation
import os
class FirstViewController: UIViewController {
#IBOutlet weak var tableview: UITableView!
#IBOutlet weak var offlineModePicture: UIBarButtonItem!
#IBOutlet weak var refresh_button: UIBarButtonItem!
var wyznania_page = 0 // page
var isNewDataLoading = false
var wyznania = [[WyznanieData](),[WyznanieData](),[WyznanieData](),[WyznanieData](),[WyznanieData]()]
let activitiyViewController = ActivityViewController(message: "Ładowanie...😇")
override func viewDidLoad() {
super.viewDidLoad()
wyznania[wyznania_page].append(WyznanieData(date: "date", story: "story", sharedLink: "link", tag: "asd", fav: false, page: 1)!)
wyznania[wyznania_page].append(WyznanieData(date: "date", story: "story", sharedLink: "link", tag: "asd", fav: false, page: 1)!)
wyznania[wyznania_page].append(WyznanieData(date: "date", story: "story", sharedLink: "link", tag: "asd", fav: false, page: 1)!)
self.navigationController?.navigationBar.sizeToFit()
view.backgroundColor = UIColor.black
tabBarController?.tabBar.barTintColor = ColorsUI.bar
tabBarController?.tabBar.tintColor = UIColor.white
navigationController?.navigationBar.barTintColor = ColorsUI.bar
navigationController?.navigationBar.tintColor = UIColor.white
let customDelegate = TableViewDelegate(dataForRows: wyznania[wyznania_page])
self.tableview.delegate = customDelegate
self.tableview.dataSource = customDelegate
}
override internal var preferredStatusBarStyle : UIStatusBarStyle {
return .lightContent
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "") {
case "ShowDetail":
guard let storyDetailViewController = segue.destination as? WyznanieViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
guard let selectedStopCell = sender as? Wyznanie else {
fatalError("Unexpected sender: \(String(describing: sender))")
}
guard let indexPath = tableview.indexPath(for: selectedStopCell) else {
fatalError("The selected cell is not being displayed by the table")
}
let selectedStory = wyznania[wyznania_page][(indexPath as NSIndexPath).row]
storyDetailViewController.wyznanie = selectedStory
default:
fatalError("Unexpected Segue Identifier; \(String(describing: segue.identifier))")
}
}
#IBAction func unwindToList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? WyznanieViewController, let story = sourceViewController.wyznanie {
if let selectedIndexPath = tableview.indexPathForSelectedRow {
// Update an existing story.
print("updating")
wyznania[wyznania_page][selectedIndexPath.row] = story
tableview.reloadRows(at: [selectedIndexPath], with: .none)
}
else {
// Add a new story
print("adding new")
}
}
}
}
And my Delegate and DataSource class:
[import UIKit
class TableViewDelegate: NSObject,UITableViewDelegate,UITableViewDataSource {
var wyznania = \[WyznanieData\]()
init(dataForRows: \[WyznanieData\]) {
self.wyznania = dataForRows
super.init()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return wyznania.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "story_cell", for:indexPath) as? Wyznanie else {
fatalError("The dequeued cell is not an instance of WyznanieTableViewCell.")
}
let wyznanie = wyznania\[indexPath.row\]
cell.date.text = wyznanie.date
cell.story.text = wyznanie.story
cell.story.setContentOffset(CGPoint.zero, animated: true)
cell.story.textColor = UIColor.white
cell.backgroundColor = ColorsUI.cell_backgroung
cell.layer.borderWidth = 3.0
cell.layer.borderColor = ColorsUI.cell_borderColor
return cell
}
}]
1
[]
Try making your delegate variable global. it must be deallocation when goes out of scope in viewDidLoad (dataSource and delegate are weak in UITableView).
Extract following declaration global.
var customDelegate: TableViewDelegate!
then in viewDidLoad do following
customDelegate = TableViewDelegate(dataForRows: wyznania[wyznania_page])

Change view by changing segmented control

I have a view UIViewController which contains two containers, one container embed to UIViewController and second to UITableViewController. UIViewController has a SegmentedControl with 3 segments. I would like to change the UITableViewController contents according to each segment. Which way is better to implement that?
Example of one of the table views: (I have two more for songs and artists)
class AlbumsSegmentView: UITableViewController{
let qryAlbums = MPMediaQuery.albums()
var allAlbums : [MPMediaItem] = []
var contentView = UIViewController()
var myMP = MPMusicPlayerController.systemMusicPlayer()
override func viewDidLoad() {
/* Background */
tableView.backgroundColor = UIColor.clear
tableView.separatorColor = UIColor.clear
/* Grouping list */
qryAlbums.groupingType = MPMediaGrouping.album
allAlbums += MPMediaQuery.albums().items!
self.clearsSelectionOnViewWillAppear = false
self.view.isHidden=true
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomTableViewCellAlbum = CustomTableViewCellAlbum(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")
cell.imageView!.clipsToBounds = true
/* For Album list */
let currLoc = qryAlbums.collectionSections![indexPath.section].range.location
let rowItem = qryAlbums.collections![indexPath.row + currLoc]
//Main text is Album name
cell.textLabel!.text = rowItem.items[0].albumTitle
cell.textLabel!.font = UIFont(name: "HelveticaNeue-Thin", size: 22)
cell.textLabel!.textColor = UIColor.white
// Detail text is Album artist
cell.detailTextLabel!.text = rowItem.items[0].albumArtist!
cell.detailTextLabel!.font = UIFont(name: "HelveticaNeue-Thin", size:18)
cell.detailTextLabel!.textColor = UIColor.white
cell.backgroundColor = UIColor.clear
let seperatorImageView = UIImageView.init(image: UIImage.init(named: "Separator.png"))
seperatorImageView.frame = CGRect(x: 10, y: cell.contentView.frame.size.height+26, width: cell.contentView.frame.size.width, height: 3)
cell.contentView.addSubview(seperatorImageView)
// Or number of songs from the current album if you prefer
//cell.detailTextLabel!.text = String(rowItem.items.count) + " songs"
// Add the album artwork
let artWork = rowItem.representativeItem?.artwork
let tableImageSize = CGSize(width: 10, height: 10) //doesn't matter - gets resized below
let cellImg: UIImageView = UIImageView(frame: CGRect(x:12,y:6,width:60,height:60))
cellImg.image = artWork?.image(at: tableImageSize)
cell.addSubview(cellImg)
return cell
}
/* For Songs list */
func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
let sectionIndexTitles = qryAlbums.itemSections!.map { $0.title }
return sectionIndexTitles
}
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
return index
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return (qryAlbums.itemSections![section].title)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allAlbums.count
//return (qryAlbums.collections?.count)!;
//return qryAlbums.collectionSections![section].range.length
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let currLoc = qryAlbums.collectionSections![indexPath.section].range.location
myMP.setQueue(with: qryAlbums.collections![indexPath.row + currLoc])
myMP.play()
}
}
class CustomTableViewCellAlbum: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
// Here you can customize the appearance of your cell
override func layoutSubviews() {
super.layoutSubviews()
// Customize imageView like you need
self.imageView?.frame = CGRect(x:12,y:6,width:60,height:60)
self.imageView?.contentMode = UIViewContentMode.scaleAspectFit
self.textLabel?.frame = CGRect(x:78, y:5, width:200, height:30)
self.detailTextLabel?.frame = CGRect(x:78, y:35, width:200, height:30)
}
}
Example of container:
class MyParentViewController: UIViewController {
var buttonPanelViewController: PanelViewController!
var tableViewController: TableViewController2!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let tableViewController = segue.destination as? TableViewController {
self.tableViewController = tableViewController
}
else if let buttonPanelViewController = segue.destination as? PanelViewController {
self.buttonPanelViewController = buttonPanelViewController
}
}
}
class TableViewController: UITableViewController {}
class PanelViewController: UIViewController {}
Example of Storyboard view
Picture example
UPD: The last thing I tried was to create 3 different UIViews above each other in MyParentViewController and attach them to different classes (Song, Artist, Album) and trying to hide one by one after switching the segments, but it didn't work. Maybe there is another and better way to implement this?
class MyParentViewController: UIViewController {
var buttonPanelViewController: PanelViewController!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let artistViewController = segue.destination as? ArtistViewController {
self.artistViewController = artistViewController
}
else if let buttonPanelViewController = segue.destination as? PanelViewController {
self.buttonPanelViewController = buttonPanelViewController
}
else if let songViewController = segue.destination as? SongViewController { self.songViewController = songViewController
}
else let albumViewController = segue.destination as? AlbumViewController { self.albumViewController = albumViewController
}
}
}
}
class TableViewController: UITableViewController {}
class PanelViewController: UIViewController {
#IBOutlet public weak var segmentTest: UISegmentedControl!
#IBAction func testAction(_ sender: UISegmentedControl) {
if (segmentTest.selectedSegmentIndex == 0){
BackTableVC().view.isHidden=false
SongsSegmentView().view.isHidden = true
AlbumsSegmentView().view.isHidden = true
print("Hi")
}else if (segmentTest.selectedSegmentIndex == 1){
BackTableVC().view.isHidden = true
SongsSegmentView().view.isHidden = false
AlbumsSegmentView().view.isHidden = true
print("Thank")
}else{
BackTableVC().view.isHidden = true
SongsSegmentView().view.isHidden = true
AlbumsSegmentView().view.isHidden = false
print("You")
}
}
}
class ArtistViewController: UIView{
} class SongViewController: UIView{
} class AlbumViewController: UIView{
}
The best way to solve this problem is to not change the table view for each segmented control, but to change the cell on each segmented control. register 3 cells for the table view controller, and once the segmented control value changes, call a delegate function to change which cell is dequeued and then update the table view with the new contents.
From the BackPanelVC, create an action from your segmented control to its class on value changed. In that action, call a delegate function with which control it has been switched to.
protocol SegmentControlChangeDelegate {
func segmentedControlChangedValue(_ value: Int)
}
class PanelViewController: UIViewController {
#IBOutlet public weak var segmentTest: UISegmentedControl!
var delegate: SegmentControlChangeDelegate?
#IBAction func testAction(_ sender: UISegmentedControl) {
delegate.segmentedControlChangedValue(value: sender.selectedSegmentIndex)
self.performSegue(with: "identifier")
}
}
And in the tableviewcontroller conform to the delegate like so:
class AlbumsSegmentView: UITableViewController, SegmentControlChangeDelegate {
func segmentedControlChangedValue(_ value: Int){
// Change which cell is being displayed with some type of boolean or switch statement that will be recalled in cell.dequeue
}
}
And make sure that you set the delegate to self in the tableviewcontroller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? PanelViewController {
destinationVC.delegate = self
}
}

SearchController issue, when search the displayController shows a spacing from the Searchbar

The issue is this:
In the storyboard, I must uncheck the Adjust Scroll View Insets, because if not do this, I will get a other issue(https://stackoverflow.com/questions/40974647/uisearchcontroller-issue-nslayoutattribute-do-not-work-in-real-device), and I don't know this if is affect the issue here.(I test in simulator, if check Adjust Scroll View Insets, the issue here will not appear )
My code
import UIKit
import SVProgressHUD
class ChooseStoreViewController: UIViewController,UISearchBarDelegate, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating {
#IBOutlet weak var tableView: UITableView!
var ori_dataSource: [StoreListModel] = [StoreListModel]()
var dataSource = [String]()
var filterdDataSource = [String]()
var resultSearchController = UISearchController()
var choosedStore:StoreListModel? = nil
var userInfoFromChooseTerant:[String:Any]?
#IBOutlet weak var top_constraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
initData()
initUI()
}
// MARK: - view life
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.isNavigationBarHidden = true
}
func initData() {
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.resultSearchController.searchBar.placeholder = "search"
self.resultSearchController.searchBar.tintColor = UIColor.black
self.resultSearchController.searchBar.delegate = self
self.tableView.tableHeaderView = self.resultSearchController.searchBar
let nib = UINib(nibName: "TerantListCell", bundle: nil)
// Required if our subclasses are to use: dequeueReusableCellWithIdentifier:forIndexPath:
//tableView.register(nib, forCellReuseIdentifier: "TerantListCell")
self.tableView.register(nib, forCellReuseIdentifier: "TerantListCell")
self.tableView.tableFooterView = UIView.init()
self.tableView.reloadData()
networkForStoreList()
}
func initUI() {
let backNavItem:UIBarButtonItem = UtilSwift.addBackButtonItem(nil, controlelr: self)
backNavItem.action = #selector(navBack)
// print(userInfoFromChooseTerant!)
tableView.separatorStyle = UITableViewCellSeparatorStyle.none
}
// MARK: - Navigation
// 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.
let chooseRole: ChooseRoleViewController = segue.destination as! ChooseRoleViewController
chooseRole.userInfoFromChooseStore = self.userInfoFromChooseTerant
}
// MARK: - search delegate
func searchBarCancelButtonClicked() {
for item:NSLayoutConstraint in self.tableView.constraints {
self.view.setNeedsLayout()
if item.firstAttribute == NSLayoutAttribute.top {
item.constant = 0
}
}
}
// MARK: - searchbar delegate
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBar.setValue("cancel", forKey:"_cancelButtonText")
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
}
// MARK: - private methods
func navBack() {
_ = self.navigationController?.popViewController(animated: true)
}
// MARK: - actions
#IBAction func unwindToChooseStoreVCFromChooseRole(segue: UIStoryboardSegue){
}
#IBAction func nextStepAction(_ sender: UIButton) {
/*if choosedStore == nil {
let lml_alert: LMLDropdownAlertView = LMLDropdownAlertView.init(frame: self.view.bounds)
lml_alert.showAlert(title: Global.hint, detail_Title: "select", cancleButtonTitle: "cacnel", confirmButtonTitle: "confirm", action: { (button) in
})
return
}*/
self.resultSearchController.isActive = false
if self.choosedStore != nil {
_ = self.userInfoFromChooseTerant?.updateValue(self.choosedStore!.userId, forKey: "store_id")
}
self.performSegue(withIdentifier: "ChooseStoreVCToChooseRoleVC", sender: self)
}
// MARK: - network
func networkForStoreList() {
let params:[String:String] = [
"createTime":"-1",
"userId" : self.userInfoFromChooseTerant!["affiliated_id"] as! String
]
// url_terantList
Mysevers.afpost(withHud: true, andAddressname: Global.url_listStore, parmas: params, requestSuccess: { (result) in
let stateCode = UtilSwift.getNetStateCode(result: result as Any, key: Global.net_key_stateCode)
if stateCode == 0 {
let storeArr:[[String : Any]] = UtilSwift.getNetAnyObject(result: result as Any, key: "list") as! [[String : Any]] // Global.net_key_bussines
//self.ori_dataSource = terantArr
for item:[String: Any] in storeArr {
let store_list_model: StoreListModel = StoreListModel.initStoreListModelWithDic(dic: item)
self.ori_dataSource.append(store_list_model)
}
for item:StoreListModel in self.ori_dataSource {
self.dataSource.append(item.name)
}
self.tableView.reloadData()
}else if stateCode == -1 {
SVProgressHUD.showError(withStatus: "err")
}
}, failBlcok: {
SVProgressHUD.showError(withStatus: "err")
})
}
// MARK: - tableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.resultSearchController.isActive {
return filterdDataSource.count
}else {
return dataSource.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TerantListCell = tableView.dequeueReusableCell(withIdentifier: "TerantListCell", for: indexPath) as! TerantListCell
if self.resultSearchController.isActive {
cell.title_label.text = self.filterdDataSource[indexPath.row]
}else {
cell.title_label?.text = self.dataSource[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.choosedStore = self.ori_dataSource[indexPath.row]
}
// MARK: - regexp
func updateSearchResults(for searchController: UISearchController) {
self.filterdDataSource.removeAll(keepingCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (self.dataSource as NSArray).filtered(using: searchPredicate)
self.filterdDataSource = array as! [String]
self.tableView.reloadData()
}
}
Go to ".storyboard" file where "ChooseStoreViewController" exist. Then click on UITableView and change tableView constraints as follows:
Check Top Space constraint.

Resources