Change view by changing segmented control - ios

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
}
}

Related

Populating fields in a custom tableView cell from data defined in a popover/popup

On the settings VC of my app I have a tableview with 5 different custom cells. The cell I’m needing some help with sets a default start and end time. This is accomplished using a popover/popup with a date/time control which is opened using a segue. This cell contains 2 buttons (start and end) and 2 fields (start and end). When one of the buttons is tapped the corresponding popover/popup is displayed. The user then defines the desired time and taps a done button. I’m using a protocol/delegate method to pass the time strings from the popover/popup. This currently works, with one exception. I want the newly defined time to populate the corresponding start or end field in he cell. My thought is to move all of this code into the custom tableViewCell where I have full access to the fields but I can’t seem to make that work. Moving the prepare(for segue: to the cell file isn’t recognized. Is there a way to perform this segue from the custom cell? Or does anybody have thoughts on populating the fields using my current setup? Thanks in advance for any help.
// All of this code is in the tableView controller except the last part where noted
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "quote_Presets_StartTime" // # 2
{
let storyboard : UIStoryboard = UIStoryboard(name: K.StoryboardID.date_Picker_SB, bundle: nil)
var popupVC : Date_Popup_VC = storyboard.instantiateViewController(withIdentifier: K.StoryboardID.date_Popup_VC) as! Date_Popup_VC
let navigationController = UINavigationController(rootViewController: popupVC)
let deviceName = UIDevice().type
let deviceString: String = ("\(deviceName)")
if deviceString.contains(K.theDevice.iPad)
{
popupVC = segue.destination as! Date_Popup_VC
if ModelData.isInline() // Wheel
{
popupVC.preferredContentSize = CGSize(width: 320, height: 300)
} else { // Inline
popupVC.preferredContentSize = CGSize(width: 350, height: 150)
}
navigationController.isNavigationBarHidden = true
} else if deviceString.contains(K.theDevice.iPhone) {
present(navigationController, animated: true, completion: nil)
navigationController.isNavigationBarHidden = false
}
popupVC.theBtn = 2
let formattedTime = ModelData.formattedTime_Read(time_Db: new_Start_Time)
popupVC.theDate = formattedTime
popupVC.timeStart_Delegate = self
} else if segue.identifier == "quote_Presets_EndTime" { // # 3
let storyboard : UIStoryboard = UIStoryboard(name: K.StoryboardID.date_Picker_SB, bundle: nil)
var popupVC : Date_Popup_VC = storyboard.instantiateViewController(withIdentifier: K.StoryboardID.date_Popup_VC) as! Date_Popup_VC
let navigationController = UINavigationController(rootViewController: popupVC)
let deviceName = UIDevice().type
let deviceString: String = ("\(deviceName)")
if deviceString.contains(K.theDevice.iPad)
{
popupVC = segue.destination as! Date_Popup_VC
if ModelData.isInline() // Wheel
{
popupVC.preferredContentSize = CGSize(width: 320, height: 300)
} else { // Inline
popupVC.preferredContentSize = CGSize(width: 350, height: 150)
}
navigationController.isNavigationBarHidden = true
} else if deviceString.contains(K.theDevice.iPhone) {
present(navigationController, animated: true, completion: nil)
navigationController.isNavigationBarHidden = false
}
popupVC.theBtn = 3
let formattedTime = ModelData.formattedTime_Read(time_Db: new_End_Time)
popupVC.theDate = formattedTime
popupVC.timeEnd_Delegate = self
}
}
// Executed from the cellForRowAt code
#objc
func setTheTimes(sender: UIButton)
{
switch sender.tag
{
case 0:
performSegue(withIdentifier: "quote_Presets_StartTime", sender: Any?.self)
case 1:
performSegue(withIdentifier: "quote_Presets_EndTime", sender: Any?.self)
default: break
}
}
extension Quote_Presets_VC: UITableViewDelegate, UITableViewDataSource
{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
// cell_05 Time
let cell_05 = tableView.dequeueReusableCell(withIdentifier: K.ReuseIdentifier.defaultTime_Cell, for: indexPath) as! DefaultTime_Cell
cell_05.backgroundColor = Theme.current.darkAccentColor
cell_05.selectionStyle = .none
cell_05.delegate = self as SaveData_Time
cell_05.label_Outlet.labelTextColorOnly_Attributes(txt: K.Titles.defaultStartEnd, txtWgt: .regular)
cell_05.labelStart_Outlet.labelTextColorOnly_Attributes(txt: K.Titles.start, txtWgt: .regular)
cell_05.labelEnd_Outlet.labelTextColorOnly_Attributes(txt: K.Titles.end, txtWgt: .regular)
cell_05.startBtn_Outlet.addTarget(self, action: #selector(setTheTimes), for: .touchUpInside)
cell_05.endButton_Outlet.addTarget(self, action: #selector(setTheTimes), for: .touchUpInside)
cell_05.fldStart_Outlet.textField_Simple(text: ModelData.timeConversionTo_12(time24: new_Start_Time))
cell_05.fldEnd_Outlet.textField_Simple(text: ModelData.timeConversionTo_12(time24: new_End_Time))
switch indexPath.row
{
case 9: // Time
cell_05.fldStart_Outlet.tag = 0
cell_05.fldEnd_Outlet.tag = 1
cell_05.startBtn_Outlet.tag = 0
cell_05.endButton_Outlet.tag = 1
return cell_05
default: break
}
return UITableViewCell()
}
}
// These are the delegates for the protocol to pass the time string
extension Quote_Presets_VC: TimeStart_DatePopupDelegate, TimeEnd_DatePopupDelegate
{
func saveTimeStart(value: String)
{
var originalValue: String = ""
for theValue in presetsArray
{
originalValue = theValue.start_Time
}
let formattedValue = ModelData.timeConversionTo_24(time12: value)
if formattedValue != originalValue
{
new_Start_Time = value
save_Array_Quote[6] = K.AppFacing_Titles.true_
} else {
save_Array_Quote[6] = K.AppFacing_Titles.false_
}
setSaveBtn_QuotePresets()
}
func clearTimeStart(value: String)
{
// startFld_Outlet.text = value
}
func saveTimeEnd(value: String)
{
var originalValue: String = ""
for theValue in presetsArray
{
originalValue = theValue.end_Time
}
let formattedValue = ModelData.timeConversionTo_24(time12: value)
if formattedValue != originalValue
{
new_End_Time = value
save_Array_Quote[7] = K.AppFacing_Titles.true_
} else {
save_Array_Quote[7] = K.AppFacing_Titles.false_
}
setSaveBtn_QuotePresets()
}
func clearTimeEnd(value: String)
{
// endFld_Outlet.text = value
}
}
// The code below is the current tableview cell code
protocol SaveData_Time: AnyObject
{
func setSaveBtn_QuotePresets()
}
class DefaultTime_Cell: UITableViewCell
{
#IBOutlet weak var label_Outlet: UILabel!
#IBOutlet weak var labelStart_Outlet: UILabel!
#IBOutlet weak var fldStart_Outlet: UITextField!
#IBOutlet weak var fldEnd_Outlet: UITextField!
#IBOutlet weak var labelEnd_Outlet: UILabel!
#IBOutlet weak var startFldHgt_Constraint: NSLayoutConstraint!
#IBOutlet weak var startBtn_Outlet: UIButton!
#IBOutlet weak var endButton_Outlet: UIButton!
weak var delegate: SaveData_Time?
override func awakeFromNib()
{
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool)
{
super.setSelected(selected, animated: animated)
}
#IBAction func startBtn_Tapped(_ sender: UIButton)
{
// This throws an error that cannot find performSegue in scope
//performSegue(withIdentifier: "quote_Presets_StartTime", sender: Any?.self)
}
#IBAction func endBtn_Tapped(_ sender: UIButton)
{
// This throws an error that cannot find performSegue in scope
//performSegue(withIdentifier: "quote_Presets_EndTime", sender: Any?.self)
}
}
First of all, don't convert your dates into strings until you need to. Pass Date values around instead.
And, to answer your question about moving more logic into your cell, don't do that. Keep your views simple.
I'd just add two closures to your DateCell to indicate if a button was tapped:
class DateCell: UITableViewCell {
#IBOutlet weak var startDateButton: UIButton!
#IBOutlet weak var endDateButton: UIButton!
var didTapStart: () -> Void = {}
var didTapEnd: () -> Void = {}
#IBAction func didSelectStart(_ sender: UIButton) {
didTapStart()
}
#IBAction func didSelectEnd(_ sender: UIButton) {
didTapEnd()
}
}
In your date editor you can use a delegate or a closure to indicate that the date changed. I'll use a closure here:
class DateEditor: UIViewController {
var dateDidChange: (Date) -> Void = { _ in }
// ...
}
I'm not a fan of segues, but if you really want to use one, then create two segues, one for editing a start date and one for editing an end date:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let cell = sender as? UITableViewCell,
let row = tableView.indexPath(for: cell)?.row,
let vc = segue.destination as? DateEditor else { return }
if segue.identifier == "editStartDate" {
vc.date = dates[row].start
} else {
vc.date = dates[row].end
}
vc.dateDidChange = { [weak self] date in
if segue.identifier == "editStartDate" {
self?.dates[row].start = date
} else {
self?.dates[row].end = date
}
self?.tableView.reloadRows(at: [IndexPath(row: row, section: 0)], with: .fade)
}
}
Whenever one of the buttons was tapped, just trigger the segue:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DateCell", for: indexPath) as! DateCell
cell.startDateButton.setTitle(
DateFormatter.awesome.string(from: self.dates[indexPath.row].start),
for: .normal
)
cell.endDateButton.setTitle(
DateFormatter.awesome.string(from: self.dates[indexPath.row].end),
for: .normal
)
cell.didTapStart = { [weak self] in
self?.performSegue(withIdentifier: "editStartDate", sender: cell)
}
cell.didTapEnd = { [weak self] in
self?.performSegue(withIdentifier: "editEndDate", sender: cell)
}
return cell
}
This is what I used for formatting dates:
extension DateFormatter {
static let awesome: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
}

Swift: How to update data in Container View without storyboard

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)
}

TextField's background color reverts back to default state a few milliseconds after it was changed in a property didSet

I'm trying to get a color selection view to pass the color to the Master view controller's textLabel. After the color is passed, it calls a didSet of a property which changes the textLabel's background color to the selected color. However, the background color visibly reverts back to the default color.
I've set breakpoints through the code and I've found that the initial viewDidLoad of the Master VC did not call when going back from the color Selector VC to the MasterVC, and the colors were correct at the end of the execution, and nothing that could override a color change ran.
However, when a new color is selected and sent back to the Master VC, the textLabel's text shows the correct color, but the color of the label is correct for a brief moment but relapses.
Does anybody know what is going on? My code is below
This is the Master View Controller. Color here is in hex, while colorName is simply the name of the color (eg. red)
class AddProjectTableViewController: UITableViewController {
// MARK: - Properties
var color: String = Color.toHex("Red")
var colorName: String? = "Red" {
didSet {
if let newColorName = colorName {
let colorHex = Color.toHex(newColorName)
self.color = colorHex
colorLabel.text = " \(newColorName)"
colorLabel.backgroundColor = Color.toUIColor(colorHex)
}
}
}
//MARK: - IBOutlets
#IBOutlet weak var colorLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
colorLabel.text = " \(colorName ?? "Choose Color")"
colorLabel.backgroundColor = Color.toUIColor(color)
print(color)
}
//MARK: - Functions
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PickColor" {
if let colorPickerVC = segue.destination as?
PickColorTableViewController{
colorPickerVC.selectedColorName = colorName
}
}
}
// MARK: - IBActions
#IBAction func unwindWithSelectedColor(segue: UIStoryboardSegue) {
if let colorPickerVC = segue.source as?
PickColorTableViewController {
guard let chosenColorName = colorPickerVC.selectedColorName,
let chosenColorHex = colorPickerVC.selectedColorHex
else { return }
self.color = chosenColorHex
self.colorName = chosenColorName
}
}
}
My colorPicker View Controller is here.
class PickColorTableViewController: UITableViewController {
// MARK: - Properties
var colors: [String] = ["Red", "Maroon", "Yellow", "Olive", "Lime",
"Green", "Aqua", "Teal", "Blue", "Navy", "Fuchsia", "Purple"]
var selectedColorName: String? {
didSet {
if let selectedColor = selectedColorName {
let index = colors.index(of: selectedColor)
selectedColorIndex = index
let selectedHex = Color.toHex(selectedColor)
selectedColorHex = selectedHex
}
}
}
var selectedColorHex: String?
var selectedColorIndex: Int?
override func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
}
//MARK: - Functions
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "SaveSelectedColor",
let cell = sender as? UITableViewCell,
let indexPath = tableView.indexPath(for: cell)
else { return }
self.selectedColorName = colors[indexPath.row]
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return colors.count
}
override func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell",
for: indexPath)
let colorName = colors[indexPath.row]
let colorHex = Color.toHex(colorName)
let color = Color.toUIColor(colorHex)
if indexPath.row == selectedColorIndex {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
cell.textLabel?.text = colorName
cell.backgroundColor = color
cell.textLabel?.textAlignment = .center
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt
indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let index = selectedColorIndex {
let cell = tableView.cellForRow(at: IndexPath(row: index,
section: 0))
cell?.accessoryType = .none
}
selectedColorName = colors[indexPath.row]
let cell = tableView.cellForRow(at: indexPath)
cell?.accessoryType = .checkmark
}
}

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])

Are there some optional wrong here?

This is the code and the problem
There is no wrong when I did select one row before I add the function tableView(......didSelectRowatindexPath...)
So, I thought it's the root cause.
I hope somebody can help me because the wrong info was not so clear that I can understand it well.
What I want to do is change the BarItemName when I did select one row of my popover table.
SwitchA is a var in my popoverviewcontroller, it means which button is pressed.
When the button in "SecondVC" is pressed,it will pass a value to SwitchA and then the popoverviewcontroller can determine which datasource it should show.
PS:this is the popoverviewcontroller's code.
import UIKit
class PopOverView: UIViewController, UITableViewDataSource, UITableViewDelegate {
var SwitchA = 0
var ClassA = ["这个类型","那个类型","这个类型","那个类型","这个类型","那个类型","这个类型","那个类型","这个类型","那个类型"]
var TimeA = ["昨天","今天","明天","昨天","今天","明天"]
var TagA = ["动漫","音乐","游戏","音乐","游戏"]
#IBOutlet weak var TV: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
TV.dataSource = self
TV.delegate = self
self.preferredContentSize = CGSize(width: UIScreen.mainScreen().bounds.width, height: 175)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if SwitchA == 0 {
return ClassA.count
}
if SwitchA == 1 {
return TimeA.count
}
else {
return TagA.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
if SwitchA == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
cell.textLabel?.text = ClassA[indexPath.row]
cell.separatorInset = UIEdgeInsetsZero
cell.preservesSuperviewLayoutMargins = false
cell.layoutMargins = UIEdgeInsetsZero
return cell
}
if SwitchA == 1 {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
cell.textLabel?.text = TimeA[indexPath.row]
cell.separatorInset = UIEdgeInsetsZero
cell.preservesSuperviewLayoutMargins = false
cell.layoutMargins = UIEdgeInsetsZero
return cell
}
else {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
cell.textLabel?.text = TagA[indexPath.row]
cell.separatorInset = UIEdgeInsetsZero
cell.preservesSuperviewLayoutMargins = false
cell.layoutMargins = UIEdgeInsetsZero
return cell
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 35.0
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: false)
if SwitchA == 0 {
let VC = self.storyboard!.instantiateViewControllerWithIdentifier("SecondVC") as! XiaoNei_HuoDong
VC.ClassName.title = ClassA[indexPath.row]
}
if SwitchA == 1 {
let VC = self.storyboard!.instantiateViewControllerWithIdentifier("SecondVC") as! XiaoNei_HuoDong
VC.TimeName.title = TimeA[indexPath.row]
}
else {
let VC = self.storyboard!.instantiateViewControllerWithIdentifier("SecondVC") as! XiaoNei_HuoDong
VC.TagName.title = TagA[indexPath.row]
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
And this is the code of "SecondVC":
import UIKit
class XiaoNei_HuoDong: UIViewController,UITableViewDelegate,UITableViewDataSource, UIPopoverPresentationControllerDelegate{
#IBOutlet weak var TagName: UIBarButtonItem!
#IBOutlet weak var TimeName: UIBarButtonItem!
#IBOutlet weak var ClassName: UIBarButtonItem!
#IBOutlet weak var huodongTV: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
huodongTV.dataSource = self
huodongTV.delegate = self
huodongTV.showsVerticalScrollIndicator = false
let options = PullToRefreshOption()
options.backgroundColor = UIColor(red: 239/255, green: 239/255, blue: 244/255, alpha: 1)
options.indicatorColor = UIColor.blackColor()
huodongTV.addPullToRefresh(options: options, refreshCompletion: { [weak self] in
// some code
self!.huodongTV.reloadData()
self!.huodongTV.stopPullToRefresh()
})
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return 3
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 1.0
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 1.0
}
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
self.huodongTV.deselectRowAtIndexPath(indexPath, animated: false)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
if indexPath.row == 0 {
let cell:HuoDong_2 = tableView.dequeueReusableCellWithIdentifier("huodong2") as! HuoDong_2
cell.ClubB1.image = UIImage(named: "test")!
cell.ClubB2.image = UIImage(named: "test")!
cell.ClubS1.image = UIImage(named: "focus")!
cell.ClubS2.image = UIImage(named: "focus")!
cell.Tag1.image = UIImage(named: "更新")!
cell.Tag2.image = UIImage(named: "更新")!
cell.View1.image = UIImage(named: "view")!
cell.View2.image = UIImage(named: "view")!
cell.Newest.image = UIImage(named: "club rank")
return cell
}
else {
let cell:HuoDong = tableView.dequeueReusableCellWithIdentifier("huodong1") as! HuoDong
cell.ClubB1.image = UIImage(named: "test")!
cell.ClubB2.image = UIImage(named: "test")!
cell.ClubS1.image = UIImage(named: "focus")!
cell.ClubS2.image = UIImage(named: "focus")!
cell.Tag1.image = UIImage(named: "更新")!
cell.Tag2.image = UIImage(named: "更新")!
cell.View1.image = UIImage(named: "view")!
cell.View2.image = UIImage(named: "view")!
return cell
}
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath){
cell.layer.transform = CATransform3DMakeScale(0.1, 0.1, 1)
UIView.animateWithDuration(0.25, animations: {
cell.layer.transform = CATransform3DMakeScale(1, 1, 1)
})
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == 0 {
return self.view.frame.width * 240.0 / 400.0
}
else {
return self.view.frame.width * 200.0 / 400.0
}
}
// MARK: - PopOverforsegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Class1"{
let VC = segue.destinationViewController as! PopOverView
VC.SwitchA = 0
VC.modalPresentationStyle = UIModalPresentationStyle.Popover
VC.popoverPresentationController?.delegate = self
}
if segue.identifier == "Time1"{
let VC = segue.destinationViewController as! PopOverView
VC.SwitchA = 1
VC.modalPresentationStyle = UIModalPresentationStyle.Popover
VC.popoverPresentationController?.delegate = self
}
if segue.identifier == "Tag1"{
let VC = segue.destinationViewController as! PopOverView
VC.SwitchA = 2
VC.modalPresentationStyle = UIModalPresentationStyle.Popover
VC.popoverPresentationController?.delegate = self
}
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
I think problem is timing of initialize.
Try this:
SecondVC
var tagNameStr = ""
override func viewDidLoad() {
super.viewDidLoad()
TagName.title = tagNameStr // here
...
}
FirstVC
if SwitchA == 0 {
let VC = self.storyboard!.instantiateViewControllerWithIdentifier("SecondVC") as! XiaoNei_HuoDong
VC.tagNameStr = ClassA[indexPath.row]
}
Hope this helps!
UPDATE
This is sample code.
(ViewController -|segue|-> SecondViewController)
class ViewController: UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let vc = segue.destinationViewController as? SecondViewController {
vc.buttonTitle = "IOhYES"
}
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var buttonItem: UIBarButtonItem!
var buttonTitle = ""
override func viewDidLoad() {
super.viewDidLoad()
buttonItem.title = buttonTitle
}
}
Please check IBOutlet connection.

Resources