I think I'm looking at some outdated code:
#IBAction func stockLevelDidChange(sender: AnyObject) {
if var currentCell = sender as? UIView {
while (true) {
currentCell = currentCell.superview!;
if let cell = currentCell as? ProductTableCell {
if let id = cell.productId? {
var newStockLevel:Int?;
if let stepper = sender as? UIStepper {
newStockLevel = Int(stepper.value);
}
else if let textfield = sender as? UITextField {
if let newValue = textfield.text.toInt()? {
newStockLevel = newValue;
}
}
if let level = newStockLevel {
products[id].4 = level;
cell.stockStepper.value = Double(level);
cell.stockField.text = String(level);
}
}
break;
}
}
displayStockTotal();
}
}
But in the first line of the function I get " '?' must be followed by a call, member lookup, or subscript" (for the question mark after as)
What does this error mean and how does this code change for Swift 1.2?
Actually the as? are all fine. The problem is this line:
if let id = cell.productId?
Just remove the question mark at the end of that. It makes no sense.
In 1.2, toInt is gone. So,
if let newValue = textfield.text.toInt()?
Should be replaced with:
if let newValue:Int? = Int(textField.text!)
The problem is the if let newValue = textfield.text.toInt()? { .. If toInt() returns an Int? then just get rid of the ? there.
Related
I am currently facing a problem where I want to use guards to enable a button if desired information are filled out.
After reading into the guard mechanism one thing is unclear for me.
How do i combine UITextFields with UISegmentedControls so that both have to be filled out/selected in order to activate the button.
#objc func editingChanged(_ textField:UITextField, sgmntControl: UISegmentedControl) {
if textField.text?.count == 1 {
if textField.text?.first == " " {
textField.text = ""
return
}
}
guard
let name = tbxName.text, !name.isEmpty,
let firstName = tbxFirstname.text, !firstName.isEmpty,
let phoneNr = tbxPhoneNr.text, !phoneNr.isEmpty,
let birthDate = tbxBirthdate.text, !birthDate.isEmpty,
let gender = sgmntGender, !gender.isSelected,
let street = tbxStreet.text, !street.isEmpty,
let postalCode = tbxPLZ.text, !postalCode.isEmpty,
let city = tbxCity.text, !city.isEmpty,
let country = tbxCountry.text, !country.isEmpty,
let idNumber = tbxIdNo.text, !idNumber.isEmpty,
let valueCheck = sgmntValueCheck, !valueCheck.isSelected,
let drivingSchoolFlyer = sgmntDrivingSchoolFlyer, !drivingSchoolFlyer.isSelected,
let visionTest = sgmntVisionTest, !visionTest.isSelected,
let visionHelp = sgmntVisionHelp, !visionHelp.isSelected
else {
rbbtnSave!.isEnabled = false
return
}
rbbtnSave!.isEnabled = true
}
I am currently not sure if my approach is correct, googling didn't help either.
I thought that it would make sense to use the UISegmentedControl just like the text field, since both are checking for booleans with isSelected and isEmpty.
I know it sounds crazy, but just curious how I can reduce the if loop iteration for following? I have tried using guard let but stucked at some place.
{
if arenaEventItems == nil || arenaEventItems.count <= 0 {
return
}
if (arenaEventItems.count > 0 && (self.arenaEvents?.monthsDictObjList.count)! > 0){
if (self.tableView != nil){
if let arrVisibleRows = self.tableView.indexPathsForVisibleRows as? [IndexPath]{
if (self.tableView.indexPathsForVisibleRows!.count > 0){
let indexPath : IndexPath = self.tableView.indexPathsForVisibleRows!.first!
if let dict = self.arenaEvents?.monthsDictObjList[indexPath.row] {
if(self.arenaHeaderView != nil) && (dict.count) > 0 {
self.arenaHeaderView?.setMonthTitle(string: (dict.keys.first!))
let selectedMonthTitle = (dict.keys.first!)
for month in (self.arenaEvents?.uniqueMonthOnlyList)! {
if (selectedMonthTitle.contains(month)){
selectedMonthIndex = (self.arenaEvents?.uniqueMonthOnlyList.index(of: month)!)!
break
}
}
}
}
}
}
}
}
}
You can reduce it like that, without any forced unwrapping or nesting:
guard let arenaEventItems = arenaEventItems,
!arenaEventItems.isEmpty,
let arenaEvents = self.arenaEvents,
!arenaEvents.monthsDictObjList.isEmpty,
let arenaHeaderView = self.arenaHeaderView,
let indexPath = self.tableView?.indexPathsForVisibleRows?.first,
let selectedMonthTitle = arenaEvents.monthsDictObjList[indexPath.row].keys.first
else {
return
}
arenaHeaderView.setMonthTitle(string: selectedMonthTitle)
if let monthIndex = arenaEvents.uniqueMonthOnlyList.index(where: { selectedMonthTitle.contains($0) }) {
selectedMonthIndex = monthIndex
}
you replace if ... return with guard !... else return to avoid nesting
you replace .count > 0 with !...isEmpty as best practice
you replace multiple access to self.something? with if let something = self.something to avoid threading issues
you unloop for ... in ... { if (...) { ... } } to .index(where: ...)
You can combine all the conditions in "if" and get something like this:
if let eventItems = arenaEventItems,
eventItems.count > 0,
let events = self.arenaEvents,
!events.monthsDictObjList.isEmpty,
let tableView = self.tableView,
let arrVisibleRows = self.tableView.indexPathsForVisibleRows as? [IndexPath],
!arrVisibleRows.isEmpty,
let indexPath : IndexPath = arrVisibleRows.first,
let dict = events.monthsDictObjList[indexPath.row],
let headerView = self.arenaHeaderView,
!dict.isEmpty {
headerView.setMonthTitle(string: (dict.keys.first!))
let selectedMonthTitle = (dict.keys.first!)
for month in events.uniqueMonthOnlyList! {
if (selectedMonthTitle.contains(month)){
selectedMonthIndex = (events.uniqueMonthOnlyList.index(of: month)!)!
break
}
}
}
You should consider restructuring your code, your code is not readable and incomprehensible for anyone who look at it. Since, you are using Swift, it is really easy to write such code with guard ... else, if ... let
pattern.
Some improvements that you can do on class is have your view non nil ie make them implicitly unwrapped optional, since you will always be connecting them to storyboard.
#IBOutlet var tableView: UITableView!
#IBOutlet var arenaHeaderView: ArenaHeaderView!
Also, you have arrays which can go to nil, why do you want it to be nil. You could simply initialize an empty array and dictionaries. That way you can reduce some more comparison code like so,
arenaEventItems: [String: String] = [:]
With that changes and a bit of refactoring, you could possibly rewrite your code to something like this,
guard !arenaEventItems.isEmpty,
let arenaEvents = arenaEvents,
let indexPath = tableView.indexPathsForVisibleRows?.first,
let dict = arenaEvents.monthsDictObjList[indexPath.row],
let selectedMonthTitle = dict.keys.first
else {
return
}
arenaHeaderView.setMonthTitle(string: selectedMonthTitle)
for month in arenaEvents.uniqueMonthOnlyList where selectedMonthTitle.contains(month) {
if let selectedIndex = arenaEvents.uniqueMonthOnlyList.index(of: month) {
selectedMonthIndex = selectedIndex
break
}
}
I have a dictionary used NSIndexPath as key. It works fine with Swift 2 but I can't find a solution to make it work with Swift 3. I don't want to use String as a key so please don't suggest it.
// init
fileprivate var cachedCellSizes: [NSIndexPath: CGSize] = [:]
// get value
if let size = cachedCellSizes[indexPath] {
return size
}
Compiler error:
Ambiguous reference to member 'subscript'
Some solution I had tried but doesn't work:
if let size:CGSize = cachedCellSizes[indexPath] as? CGSize {
return size
}
if let size:CGSize = cachedCellSizes[indexPath] as! CGSize {
return size
}
if let size:CGSize = cachedCellSizes["indexPath"] as CGSize {
return size
}
fileprivate var cachedCellSizes: [NSIndexPath: CGSize] = [:]
if let size = cachedCellSizes[indexPath as NSIndexPath] {
print(indexPath)
}
OR
fileprivate var cachedCellSizes: [IndexPath: CGSize] = [:]
if let size = cachedCellSizes[indexPath] {
print(indexPath)
}
I just finished converting this project to Swift 2 and had a ton of optionals to fix. So naturally there were bound to be some runtime issues. Console is giving the usual optional error message. I have a data model and other classes though so I'm having difficulty sourcing the problem/s. So far I think there is only this one issue though. Could you please suggest proper optional declaration / unwrapping. I know what they mean but since I'm fairly green I need to be shown what to fix even though I know the definition of optionals. I realize the images in the navigation window are optionals. They definitely exist so they should be unwrapped. So is there a problem with nickname or SpaceObject class?
SpaceObject.swift
class SpaceObject: NSObject {
var name: String?
var gravitationalForce: Float?
var diameter: Float?
var yearLength: Float?
var dayLength: Float?
var temperature: Float?
var numberOfMoons: Int?
var nickname: String?
var interestingFact: String?
var spaceImage: UIImage?
override init() {
}
init(initWithData data:NSDictionary, andImage image:UIImage) {
if (data[PLANET_NAME] != nil) { self.name = String(format: data[PLANET_NAME] as! NSString as String) }
if (data[PLANET_GRAVITY] != nil) { self.gravitationalForce = Float(data[PLANET_GRAVITY] as! NSNumber) }
if (data[PLANET_DIAMETER] != nil) { self.diameter = Float(data[PLANET_DIAMETER] as! NSNumber) }
if (data[PLANET_YEAR_LENGTH] != nil) { self.yearLength = Float(data[PLANET_YEAR_LENGTH] as! NSNumber) }
if (data[PLANET_DAY_LENGTH] != nil) { self.dayLength = Float(data[PLANET_DAY_LENGTH] as! NSNumber) }
if (data[PLANET_TEMPERATURE] != nil) { self.temperature = Float(data[PLANET_TEMPERATURE] as! NSNumber) }
if (data[PLANET_NUMBER_OF_MOONS] != nil) { self.numberOfMoons = Int(data[PLANET_NUMBER_OF_MOONS] as! NSNumber) }
if (data[PLANET_NICKNAME] != nil) { self.nickname = String(format: data[PLANET_NICKNAME] as! NSString as String) }
if (data[PLANET_INTERESTING_FACT] != nil) { self.interestingFact = String(format: data[PLANET_INTERESTING_FACT] as! NSString as String) }
self.spaceImage = image
}
}
SpaceDataViewController.swift
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DataCell", forIndexPath: indexPath) as UITableViewCell
switch indexPath.row {
case 0:
cell.textLabel!.text = "Nickname:"
if let nickname = self.spaceObject?.nickname {
cell.detailTextLabel!.text = "\(nickname)"
}
case 1:
cell.textLabel!.text = "Diameter (km):"
if let diameter = self.spaceObject?.diameter {
cell.detailTextLabel!.text = "\(diameter)"
}
case 2:
cell.textLabel!.text = "Gravitational Force:"
if let gravitationalForce = self.spaceObject?.gravitationalForce {
cell.detailTextLabel!.text = "\(gravitationalForce)"
}
case 3:
cell.textLabel!.text = "Length of a Year (in days):"
if let yearLength = self.spaceObject?.yearLength {
cell.detailTextLabel!.text = "\(yearLength)"
}
case 4:
cell.textLabel!.text = "Length of a Day (in hours):"
if let dayLength = self.spaceObject?.dayLength {
cell.detailTextLabel!.text = "\(dayLength)"
}
case 5:
cell.textLabel!.text = "Temperature (in celsius):"
if let temperature = self.spaceObject?.temperature {
cell.detailTextLabel!.text = "\(temperature)"
}
case 6:
cell.textLabel!.text = "Number of Moons:"
if let numberOfMoons = self.spaceObject?.numberOfMoons {
cell.detailTextLabel!.text = "\(numberOfMoons)"
}
case 7:
cell.textLabel!.text = "Interesting Fact:"
if let interestingFact = self.spaceObject?.interestingFact {
cell.detailTextLabel!.text = "\(interestingFact)"
}
default: break
}
return cell
OuterSpaceTableViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
for planetData in AstronomicalData.allKnownPlanets() {
let imageName = "\(planetData[PLANET_NAME]).jpg"
let planet = SpaceObject(initWithData: planetData as! NSDictionary, andImage: UIImage(named: imageName)!) // Mercury.jpg is an optional
self.planets += [planet]
}
if let spaceList = self.userDefaults.arrayForKey(ADDED_SPACE_OBJECTS_KEY) {
for spaceObjectItem in spaceList {
self.addedSpaceObjects += [self.spaceObjectForDictionary(spaceObjectItem)]
}
}
}
Swift-Strings-Extension.swift
extension String {
// Convert string to floats.
func toFloat() -> Float? {
return (self as NSString).floatValue
}
}
Solution to the error that you have posted is to unwrap the planet name string you fetch from the dictionary. Since its an optional, its returning the value as "Optional(Mercury)". So when you don't unwrap, the image name gets formed as "Optional(Mercury).jpg" which will obviously not be available in your resources folder.
Example:
for planetData in AstronomicalData.allKnownPlanets {
if let planetName = planetData[PLANET_NAME] as! String {
let imageName = "\(planetName).jpg" //Now it will be Mercury.jpg
}
//...
}
If you find more errors after this, they should be dealt on case by case basis.
I want to be able to retrieve this string in one of my functions... but thats not really the problem I've done that before. But this time instead the string is defined as and if let inside the function and can't be found anywhere else inside the function. I want to be able to use that same value (fileloaction) a string outside of that function but it can't be found.
Here's my code:
func extract_json(data:NSString)
{
var parseError: NSError?
let jsonData:NSData = data.dataUsingEncoding(NSASCIIStringEncoding)!
let json: AnyObject?
do {
json = try NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
} catch let error as NSError {
parseError = error
json = nil
}
if (parseError == nil)
{
if let works_list = json as? NSArray
{
for (var i = 0; i < works_list.count ; i++ )
{
if let fileList = works_list[i] as? NSDictionary
{
if let fileName = fileList["filename"] as? String
{
TableData1.append(fileName)
//if let country_code = country_obj["post_text"] as? String
//{
//TableData1.append(country_name + " [" + country_code + "]")
//}
}
this is the function
if let filelocation = fileList["filelocation"] as? String
{
TableData1.append(filelocation)
}
if let mime = fileList["mime"] as? String
{
TableData1.append(mime)
}
}
}
}
}
do_table_refresh();
}
That string fileloaction cant be found anywhere else in that function so for that reason I can't use it anywhere else that I'd need to like here:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
extract_json(filelocation)
// selectedFileLocation = "http://192.168.1.14:8080/Works/uploads/1445557983_putty.docx"
selectedFileLocation = filelocation
if(segue.identifier == "detailView") {
let vc = segue.destinationViewController as! DisplayWorksViewController
vc.selectedFileLocation = selectedFileLocation
vc.selectedLabel = selectedLabel
print("selectedFileLocation = \(vc.selectedFileLocation)")
}
}
The scope of the fileLocation variable is limited within your function extract_json if block.
To access the value of it across all the functions/ methods of the view controller, Create a variable in your controller and assign the fileLocation value to it. Later use that variable to access.
Hope it helps.