8 Replies
      Latest reply: Sep 30, 2016 7:19 PM by waynehend RSS
      waynehend Level 1 Level 1 (5 points)

        Apple's code for the HomeKit Catalog app is available for Swift 2.3 as of the 9/13/16 release.  I'm trying to use some of that code (the HMCharacteristic extension) in my app, which is all in Swift 3.

         

        Much of the HMCharacteristic extension converts to Swift 3 successfully.

         

        The problem I'm seeing centers on the NumberFormatter.  I cannot seem to get the syntax right for the conversion of a number to a string.

         

        Here's the extension as provided by Apple for Swift 2.3.  The problem is in line #40.

        import HomeKit
        extension HMCharacteristic {
        
            private struct Constants {
                static let valueFormatter = NSNumberFormatter()
                static let numericFormats = [
                    HMCharacteristicMetadataFormatInt,
                    HMCharacteristicMetadataFormatFloat,
                    HMCharacteristicMetadataFormatUInt8,
                    HMCharacteristicMetadataFormatUInt16,
                    HMCharacteristicMetadataFormatUInt32,
                    HMCharacteristicMetadataFormatUInt64
                ]
            }
        
            /*
                Returns the localized description for a provided value, taking the characteristic's metadata and possible
                values into account.
        
                - parameter value: The value to look up.
        
                - returns: A string representing the value in a localized way, e.g. `"24%"` or `"354º"`
            */
            func localizedDescriptionForValue(value: AnyObject) -> String {
                if self.isWriteOnly {
                    return NSLocalizedString("Write-Only Characteristic", comment: "Write-Only Characteristic")
                }
                else if self.isBoolean {
                    if let boolValue = value.boolValue {
                        return boolValue ? NSLocalizedString("On", comment: "On") : NSLocalizedString("Off", comment: "Off")
                    }
                }
                if let number = value as? Int {
                    if let predeterminedValueString = self.predeterminedValueDescriptionForNumber(number) {
                        return predeterminedValueString
                    }
            
                    if let stepValue = self.metadata?.stepValue {
                        Constants.valueFormatter.minimumFractionDigits = Int(log10(1.0 / stepValue.doubleValue))
                        if let string = Constants.valueFormatter.stringFromNumber(number) {
                            return string + self.localizedUnitDecoration
                        }
                    }
                }
                return "\(value)"
            }
        
        

         

        The line that causes trouble in Swift 3 is #03 below, where the integer "number " is to be converted to a string:

         

                    if let stepValue = self.metadata?.stepValue {
                        Constants.valueFormatter.minimumFractionDigits = Int(log10(1.0 / stepValue.doubleValue))
                        if let string = Constants.valueFormatter.string(from: number) {
                            return string + self.localizedUnitDecoration
                        }
        
        

         

        I can't this to work in Playground either:

        import UIKit
        let valueFormatter = NumberFormatter()
        let value = 23
        if let myNumber = value as? Int {
            print("myNumber is \(myNumber)")   <-- "myNumber is 23"
        
            if let string = valueFormatter.string(from number: myNumber) {
        print("myNumber is \(myNumber) and the string is \(string)")
            }
        }
        
        

         

        error: NumberFormatter testing.playground:6:49: error: cannot convert value of type 'Int' to expected argument type 'NSNumber'

            if let string = valueFormatter.string(from: myNumber) {

         

        In addition to the syntax above, I've tried the suggestion:

            if let string = valueFormatter.string(from: NSNumber(myNumber))
        
        

         

            if let string = valueFormatter.string(from:myNumber)
        
        

         

        Nothing is working!

        • Re: Problems with Homekit Catalog in Swift 3
          OOPer Level 7 Level 7 (3,565 points)

          Try this:

              if let string = valueFormatter.string(from: myNumber as NSNumber)
          

           

          Most implicit type conversions, like `Int` to `NSNumber`, are removed from Swift 3, and when you need such bridging conversion, you can use `as` explicitly. (Not `as?` nor `as!`.)

           

          Or else you can construct an NSNumber with:

              if let string = valueFormatter.string(from: NSNumber(value: myNumber))
          
            • Re: Problems with Homekit Catalog in Swift 3
              waynehend Level 1 Level 1 (5 points)

              That cleared the error.  Thanks!.

               

              Now there are only a couple remaining issues.  Lines 04 and 12 below cause an error "Cannot invoke initializer for type 'Bool' with an argument list of type '(Int)'"

               

              These are the next few lines following those in the first post.

               

                  func predeterminedValueDescriptionForNumber(number: Int) -> String? {
                      switch self.characteristicType {
                      case HMCharacteristicTypePowerState, HMCharacteristicTypeInputEvent, HMCharacteristicTypeOutputState:
                          if Bool(number) {
                              return NSLocalizedString("On", comment: "On")
                          }
                          else {
                              return NSLocalizedString("Off", comment: "Off")
                          }
                     
                      case HMCharacteristicTypeOutletInUse, HMCharacteristicTypeMotionDetected, HMCharacteristicTypeAdminOnlyAccess, HMCharacteristicTypeAudioFeedback, HMCharacteristicTypeObstructionDetected:
                          if Bool(number) {
                              return NSLocalizedString("Yes", comment: "Yes")
                          }
                          else {
                              return NSLocalizedString("No", comment: "No")
                          }
                         }
              
              
              

               

               

              Much later in the extension, this variable definition also has a type problem.  Line 07 throws the error:  "Result valueof type 'Int" does not conform to closure result type 'AnyObject'

              [UPDATE]  This error goes away by replacing [AnyObject] with [Any]

               

                     /// - returns:  All of the possible values that this characteristic can contain.
                       var allPossibleValues: [AnyObject]?
                      guard self.isInteger else { return nil }
                      guard let metadata = metadata, let stepValue = metadata.stepValue as? Double else { return nil }
                      let choices = Array(0..<self.numberOfChoices)
                      return choices.map { choice in
                          Int(Double(choice) * stepValue)
                      }
              
              
              
                • Re: Problems with Homekit Catalog in Swift 3
                  eskimo Apple Staff Apple Staff (7,550 points)

                  Lines 04 and 12 below cause an error …

                  I was unsure how Bool(number) ever worked, so I tried it on Swift 2.  It only works when you import Foundation, presumably via implicit Int-to-NSNumber then explicitly NSNumber-to-Bool conversions.  Yowsers!

                  Regardless, the correct way to do this, in both Swift 2 and Swift 3, is:

                  if number != 0 {
                      …
                  }
                  

                  Share and Enjoy

                  Quinn “The Eskimo!”
                  Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                  let myEmail = "eskimo" + "1" + "@apple.com"

              • Re: Problems with Homekit Catalog in Swift 3
                Marcus Level 1 Level 1 (0 points)

                I've another question regarding the HMCatalog and the new Swift 3 Syntax.

                 

                Lines 7 throws the error 'Generic parameter 'TargetValueType' could not be inferred in cast to HMCharacteristicWriteAction<_>'. I do not understand at all what happened there since HMCharacteristicWriteAction is a subclass of HMAction.

                 

                    func targetValueForCharacteristic(_ characteristic: HMCharacteristic) -> AnyObject? {
                        if let value = targetValueMap.object(forKey: characteristic) {
                            return value
                        }
                        else if let actions = actionSet?.actions {
                            for action in actions {
                                if let writeAction = action as? HMCharacteristicWriteAction
                                    , writeAction.characteristic == characteristic {
                                        return writeAction.targetValue
                                }
                            }
                        }
                        return nil
                    }
                
                  • Re: Problems with Homekit Catalog in Swift 3
                    waynehend Level 1 Level 1 (5 points)

                    Where is that code from, maybe an older version?  I cannot find it in the latest HMCatalog release.

                      • Re: Problems with Homekit Catalog in Swift 3
                        Marcus Level 1 Level 1 (0 points)

                        It's in ActionSetCreator.swift from the last version (2.2) - however migrated to Swift 3 by Xcode. The original code is:

                         

                            func targetValueForCharacteristic(characteristic: HMCharacteristic) -> AnyObject? {
                                if let value = targetValueMap.objectForKey(characteristic) {
                                    return value
                                }
                                else if let actions = actionSet?.actions {
                                    for action in actions {
                                        if let writeAction = action as? HMCharacteristicWriteAction
                                            where writeAction.characteristic == characteristic {
                                                return writeAction.targetValue
                                        }
                                    }
                                }
                        
                        
                                return nil
                            }