please barer with me that I am completely new to swift and iOS..
I'm trying to make a "team select" on my share extension. Ideally I want to be able to tap the "Team" footer and select multiple teams from a table view, and at last share/post to the selected teams.
I have been following a tutorial from 2016, but I think it is outdated by now unfortunately, and I haven't been able to find one similar that is up to date. (if you know one please link)
I have created a TeamTableViewController.swift (UITableViewController) with a hardcoded teamList which I hope to populate in the share extension.
my UITableViewController file looks like this:
import UIKit
protocol TeamViewProtocol {
func sendingViewController(viewController: TeamTableViewController, sentItem: String)
}
class TeamTableViewController: UITableViewController {
var teamList: [String] = ["Team 1", "Team 2", "Team 3", "Team 4", "Team 5"]
var delegate: TeamViewProtocol?
override func viewDidLoad() {
super.viewDidLoad()
self.clearsSelectionOnViewWillAppear = false
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.teamList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TeamCell", for: indexPath)
cell.textLabel!.text = self.teamList[indexPath.item]
return cell
}
}
My ShareViewController filer looks like this:
import UIKit
import Social
class ShareViewController: SLComposeServiceViewController, TeamViewProtocol {
var item: SLComposeSheetConfigurationItem!
var teamPickerVC: TeamTableViewController!
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
return true
}
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
override func configurationItems() -> [Any]! {
self.item = SLComposeSheetConfigurationItem()
self.item.title = "Team"
self.item.value = "None"
self.item.tapHandler = {
self.teamPickerVC = TeamTableViewController()
self.pushConfigurationViewController(self.teamPickerVC)
}
return [self.item]
}
func sendingViewController(viewController: TeamTableViewController, sentItem: String) {
self.item.value = sentItem
self.popConfigurationViewController()
}
}
When I tap the extension window footer "Team" the entire extension dismisses with no error message. If however, I set the hard coded teamList array to an empty array, then the extensions does not crash/dismiss but instead shows the table view with empty rows.
What am I missing in order for my hard coded teams to show as cell/lines/rows? in the table view?
You should return the proper number of sections or can remove that method, so it will take the default value of numerOfSections as 1
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
Hope it helps
After a lot of trial and error. I finally found a forum post about the correct implementation of the tableView function that calls with the argument cellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "TeamCell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "TeamCell")
}
cell!.textLabel!.text = self.teamList[indexPath.item]
return cell!
}
This was not easy to debug, and there were no error or exceptions printed.
You also have to implement the didSelectRowAt delegate for the tableView to be able to send the selected item back to the main view.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedItem = self.teamList[indexPath.item]
delegate?.sendingViewController(viewController: self, sentItem: selectedItem)
self.navigationController?.popViewController(animated: true)
}
Related
I have a data source in this form:
struct Country {
let name: String
}
The other properties won't come into play in this stage so let's keep it simple.
I have separated ViewController and TableViewDataSource in two separate files. Here is the Data source code:
class CountryDataSource: NSObject, UITableViewDataSource {
var countries = [Country]()
var filteredCountries = [Country]()
var dataChanged: (() -> Void)?
var tableView: UITableView!
let searchController = UISearchController(searchResultsController: nil)
var filterText: String? {
didSet {
filteredCountries = countries.matching(filterText)
self.dataChanged?()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredCountries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let country: Country
country = filteredCountries[indexPath.row]
cell.textLabel?.text = country.name
return cell
}
}
As you can see there is already a filtering mechanism in place.
Here is the most relevant part of the view controller:
class ViewController: UITableViewController, URLSessionDataDelegate {
let dataSource = CountryDataSource()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.tableView = self.tableView
dataSource.dataChanged = { [weak self] in
self?.tableView.reloadData()
}
tableView.dataSource = dataSource
// Setup the Search Controller
dataSource.searchController.searchResultsUpdater = self
dataSource.searchController.obscuresBackgroundDuringPresentation = false
dataSource.searchController.searchBar.placeholder = "Search countries..."
navigationItem.searchController = dataSource.searchController
definesPresentationContext = true
performSelector(inBackground: #selector(loadCountries), with: nil)
}
The loadCountries is what fetches the JSON and load the table view inside the dataSource.countries and dataSource.filteredCountries array.
Now, how can I get the indexed collation like the Contacts app has without breaking all this?
I tried several tutorials, no one worked because they were needing a class data model or everything inside the view controller.
All solutions tried either crash (worst case) or don't load the correct data or don't recognise it...
Please I need some help here.
Thank you
I recommend you to work with CellViewModels instead of model data.
Steps:
1) Create an array per word with your cell view models sorted alphabetically. If you have data for A, C, F, L, Y and Z you are going to have 6 arrays with cell view models. I'm going to call them as "sectionArray".
2) Create another array and add the sectionArrays sorted alphabetically, the "cellModelsData". So, The cellModelsData is an array of sectionArrays.
3) On numberOfSections return the count of cellModelsData.
4) On numberOfRowsInSection get the sectionArray inside the cellModelsData according to the section number (cellModelsData[section]) and return the count of that sectionArray.
5) On cellForRowAtindexPath get the sectionArray (cellModelsData[indexPath.section]) and then get the "cellModel" (sectionArray[indexPath.row]). Dequeue the cell and set the cell model to the cell.
I think that this approach should resolve your problem.
I made a sample project in BitBucket that could help you: https://bitbucket.org/gastonmontes/reutilizablecellssampleproject
Example:
You have the following words:
Does.
Any.
Visa.
Count.
Refused.
Add.
Country.
1)
SectionArrayA: [Add, Any]
SectionArrayC: [Count, Country]
SectionArrayR: [Refused]
SectionArrayV: [Visa]
2)
cellModelsData = [ [SectionArrayA], [SectionArrayC], [SectionArrayR], [SectionArrayV] ]
3)
func numberOfSections(in tableView: UITableView) -> Int {
return self.cellModelsData.count
}
4)
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionModels = self.cellModelsData[section]
return sectionModels.count
}
5)
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionModels = self.cellModelsData[indexPath.section]
let cellModel = sectionModels[indexPath.row]
let cell = self.sampleCellsTableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier",
for: indexPath) as! YourCell
cell.cellSetModel(cellModel)
return cell
}
I am new to Swift , I am parsing my JSON by using ObjectMapper but I want data to be displayed in TableView. But I have a problem:
libc++abi.dylib: terminating with uncaught exception of type NSException
I get it after the method numberOfRowsInSection. My array is not nil, array has a 2193 elements
I do not understand why it happened
It my code for parsing JSON :
let timeStamp = NSNumber(value: Date().timeIntervalSinceNow)
var programs = [PrograToDayModel]()
override func viewDidLoad() {
super.viewDidLoad()
super.viewDidLoad()
let timeStamp = NSNumber(value: Date().timeIntervalSinceNow)
self.downloadPrograms(for: timeStamp)
}
func downloadPrograms(for timestamp: NSNumber) {
Alamofire.request("http://52.50.138.211:8080/ChanelAPI/programs/\(timestamp)").responseArray { (response: DataResponse<[PrograToDayModel]>) in
let programlArray = response.result.value
if let programlArray = programlArray {
for program in programlArray {
self.programs.append(program)
print(program.title as Any)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
it good i print element in console :
my code for table:
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
print(self.programs.count as Any)
return self.programs.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProgramTableViewCell", for: indexPath) as! ProgramTableViewCell
cell.title.text = self.programs[indexPath.row].title
return cell
}
}
All identifiers in place
I using tab bar, tableView, tableViewCell
How can I solve this problem?
To identify the issue, you can just try this -
it might be a reason for that issue
So go to Main.storyboard, and right-click on View Controller at the top of the phone outline and remove any outlets with yellow flags (if any).
I was getting a similar non descriptive error when trying to initialize a uitableviewcontroller when trying to add a section/number of rows. Did you register a tableview cell class? I see that you have a custom tableview cell created, so if that isn't registered with your tableview that might be causing this error.
tableView.register("ProgramTableViewCell".self, forCellReuseIdentifier: "ProgramTableViewCell")
I'm just in the process of putting a prototype app together in Swift 3.0.
At the minute, all API information is Parsed in to table cells. What I was hoping to do was introduce the facility where if I set an "Active" column in my database to "no" then how could I prevent that cell from showing in the table view?
Currently the code is this:
func configure(offence: Offence) {
if let name = offence.name, let act = offence.act {
self.textLabel?.text = name
self.detailTextLabel?.text = act
}
I've tried playing around with if/else statements but I'm not having much luck.
if your Offences have an active property you can use it to filter your server response and only show those with the active property set to true. something like this:
struct Offence {
var name: String
var active: Bool
}
class YourViewController: UITableViewController {
// all the offences you get from the server
let offences: [Offence] = []
// only the active offences - use those as your datasource!
var visibleOffences: [Offence] {
return offences.filter { $0.active }
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return visibleOffences.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourCell", for: indexPath)
cell.textLabel?.text = visibleOffences[indexPath.row].name
return cell
}
}
I have a TableViewController (lets call TVC1) with a row that says "OD" (which stands for Outer Diameter).
Upon selecting this row, a bunch of rows in a new TableViewController (lets call TVC2) containing the various OD (casingOD in my code) shows. What I want to happen is when the user selects the OD it will segue back to the main TableViewController with the string that corresponds to the user selection. My code for this currently fails...Could anyone help point me in the right direction? If you require TVC1 code i'll happily post it, i'm just trying to save any unneccessary code reading for you folks :)
My TVC2 code is as follows:
import UIKit
class CasingSelectionTableViewController: UITableViewController {
var selectedData: Data?
let casingOD = ["114.3", "127.0", "139.7", "168.3" , "177.8", "193.7", "219.1", "244.5", "247.6", "273.1", "298.4", "298.4", "339.7", "406.4", "473.0", "508"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
switch selectedData! {
case .OuterDiameter:
print(casingOD)
case .Weight:
print(casingWeight114) // I deleted the casingWeight114 line of code as its not required for this question
case .InnerDiameter:
print(id114) // I deleted the id114 line as its not required for this question
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return casingOD.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var casingSpec: UITableViewCell!
if selectedData == Data.OuterDiameter {
casingSpec = tableView.dequeueReusableCellWithIdentifier("selectedCasingSpec", forIndexPath: indexPath)
let casingODSpec = casingOD[indexPath.row]
casingSpec.textLabel?.text = casingODSpec
return casingSpec
} else {
return casingSpec
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selection: UITableViewCell!
selection.textLabel?.text = indexPath.row as! String
}
What I want to happen is when the user selects the OD it will segue back to the main TableViewController with the string that corresponds to the user selection.
First of all you'll need to implement a way for TVC2 to notify TVC1 that a value has been selected.
A common way to do such thing is by using delegation. You can define a delegate protocol like this:
protocol TVC2Delegate {
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String)
}
Then add a var delegate: TVC2Delegate? property to TVC2.
You'll then make TVC1 comform to TVC2Delegate by implementing that method in TVC1.
When presenting TVC2 from TVC1 remember to set it as the delegate for TVC2.
// In TVC1
tvc2.delegate = self
To connect TVC1 and TVC2 you could add a bit o logic to your tableView(tableView:,didSelectRowAtIndexPath:) method call the delegate with the selected value
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let stringValue = indexPath.row as! String
// Do anything you need to do related to TVC2 here.
// Then finally
delegate?.tvc2(self, didSelectOuterDiameter: stringValue)
}
Finally, in TVC1's implementation of the delegate method you can take care of dismissing TVC2 if needed.
Update:
This is how the final implementation of these bits might look like:
// In TVC1
class TVC1: UITableViewController, TVC2Delegate {
// ...
// Implement the method(s) of TVC2Delegate
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String) {
// Do whatever you need to do with the outerDiameter parameter
}
}
// In TVC2
protocol TVC2Delegate {
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String)
}
class CasingSelectionTableViewController: UITableViewController {
var delegate: TVC2Delegate?
// ...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let stringValue = casingOD[indexPath.row]
// Do anything you need to do related to TVC2 here.
// Then finally
delegate?.tvc2(self, didSelectOuterDiameter: stringValue)
}
}
Use the delegate approach as suggested in the answer by #Mokagio. And in case you're having issue in getting the string, here is the answer
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! UITableViewCell
let stringValue = cell.textLabel.text //You can get this from your datasource as well)
//call the delegate
}
I am trying to build a list within a table view controller and I have the proper setup, but for some reason my simulator crashes at the line where I set my array. It is not an error, but a Thread issue. I'm still learning the XCode warning system so I'm not sure what that means, but I noticed that in the Thread notifications that cityArray = ([String]) 0 values. Can anyone help?
import UIKit
class ListTableViewController: UITableViewController {
var cityArray: [String] = ["Portland","San Francisco","Cupertino"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cityArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = self.cityArray[indexPath.row]
return cell
}
}
UPDATE:
Images of the Thread message:
try this
let cityArray: [NSArray] = ["Portland","San Francisco","Cupertino"] as NSArray
Can you also provide the error you're receiving? Is it an issue with the array being mutated while being enumerated or something?
Also,
var cityArray: [String] = ["Portland","San Francisco","Cupertino"]
can change to
var cityArray = ["Portland", "San Francisco", "Cupertino"]
Since all your objects are of the same type, the Swift's type inference takes care of this for you.