We are using Introductory Prices on our app. And we have an issue only reproducible on one of our two QA devices which is an iPhone 6S (11.4.1) on the French App Store. The other is an iPhone 7 (12.0 with French App Store) and the app is not crashing.
We are using this extension based on the SKProduct extension provided by SwiftyStoreKit :
#available(iOS 11.2, *)
public extension SKProductDiscount {
public var localizedPrice: String? {
return priceFormatter(locale: priceLocale).string(from: price)
}
private func priceFormatter(locale: Locale) -> NumberFormatter {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.numberStyle = .currency
return formatter
}
}
Used like this :
func updateWith(storeProducts: Set<SKProduct>) {
guard
let selfStoreInfo = storeProducts.filter({ $0.productIdentifier == self.id }).first else {
Logger.warn(message: "Subscription \(self.id) not found on store", .inAppPurchase)
return
}
if #available(iOS 11.2, *) {
if let promo = selfStoreInfo.introductoryPrice {
promotionId = selfStoreInfo.productIdentifier
price = promo.localizedPrice
originalPrice = selfStoreInfo.localizedPrice
} else {
price = selfStoreInfo.localizedPrice
}
} else {
price = selfStoreInfo.localizedPrice
}
}
When debugging we found that priceLocale is responsible for throwing the EXC_BREAKPOINT.
EDIT Could be linked to this : https://bugs.swift.org/browse/SR-7922?attachmentOrder=desc but it's strange that it would work on our iPhone 7 and not on the iPhone 6s
Try this:
DispatchQueue.global(qos: .default).async {
while true {
if !SKProduct().productIdentifier.isEmpty {
if let productPriceString: String = SC.storeProduct.localizedPrice {
DispatchQueue.main.async {
print(productPriceString)
}
}
break
}
// Some wait process like using "semaphore"
}
Replace SKProduct() with your product.
In my case, this occurs when the function accessed before the product initialized.
Related
I'm working on a watchOS App as my first Swift/iOS project ever. I want to fetch the latest body weight sample and use it for some calculation. The result is presented to the user. As soon as a new sample is added, I want to update my UI as well. It works in a completely fresh simulator installation. As soon as I add a sample in the iOS simulator, the app updates its UI in the watchOS simulator. However, it doesn't work on my real device or after resetting the watchOS simulator. And I just don't know why. The HKAnchoredObjectQuery just returns 0 samples but I definitely have some samples stored in health. I can even see them under Settings > Health on my watch. I can't imagine this is related to my code, but here it is:
class WeightProvider: ObservableObject {
private static let weightSampleType = HKSampleType.quantityType(forIdentifier: .bodyMass)!
private static let healthStore: HKHealthStore = .init()
private var previousAnchor: HKQueryAnchor?
private var runningQuery: HKAnchoredObjectQuery?
#Published var bodyWeight: Measurement<UnitMass>?
func getBodyWeight(longRunning: Bool = false) {
let query = HKAnchoredObjectQuery(type: Self.weightSampleType, predicate: nil, anchor: previousAnchor, limit: longRunning ? HKObjectQueryNoLimit : 1, resultsHandler: processQueryResult)
if longRunning {
query.updateHandler = processQueryResult
runningQuery = query
}
Self.healthStore.execute(query)
}
func stopLongRunningQuery() {
if let runningQuery = runningQuery {
Self.healthStore.stop(runningQuery)
self.runningQuery = nil
}
}
private func processQueryResult(_: HKAnchoredObjectQuery, samples: [HKSample]?, _: [HKDeletedObject]?, newAnchor: HKQueryAnchor?, error: Error?) {
guard let samples = samples as? [HKQuantitySample], error == nil else {
fatalError(error?.localizedDescription ?? "Failed to cast [HKSample] to [HKQuantitySample]")
}
previousAnchor = newAnchor
guard let sample = samples.last else {
return
}
DispatchQueue.main.async {
if Locale.current.usesMetricSystem {
let weight = sample.quantity.doubleValue(for: .gramUnit(with: .kilo))
self.bodyWeight = .init(value: weight, unit: UnitMass.kilograms)
} else {
let weight = sample.quantity.doubleValue(for: .pound())
self.bodyWeight = .init(value: weight, unit: UnitMass.pounds)
}
}
}
}
// MARK: - HealthKit Authorization
extension WeightProvider {
private static let typesToRead: Set<HKObjectType> = [
weightSampleType,
]
func authorize(completion: #escaping (Bool, Error?) -> Swift.Void) {
Self.healthStore.requestAuthorization(toShare: nil, read: Self.typesToRead) { success, error in
completion(success, error)
}
}
}
In my Views onAppear I call this function:
private func authorizeHealthKit() {
guard firstRun else {
return
}
firstRun = false
weightProvider.authorize { success, error in
guard success, error == nil else {
return
}
weightProvider.getBodyWeight(longRunning: true)
}
}
HealthKit is properly authorized as I can see in the Settings of my Watch. Any ideas? Any tips for my code in general?
Wow, after all this time I found the issue: The line previousAnchor = newAnchor needs to be after the guard statement. That's it.
Before going in-depth, let me tell you all that - Yes, I've gone through all possible solutions provided on Stack-Overflow.
Problem Statement : I'm not able to read 'CarrierName' of my available SIM using iPhone
What did I tried : I've tried two different solutions, but I'm unable to read CarrierName.
Solution 1 : When I tried this solution I've received only "Carrier" as output, instead of CarrierName.
Solution 1 :
//--------------- CodeBase : Solution1 -----------------
let networkInfo = CTTelephonyNetworkInfo()
let carrier = networkInfo.serviceSubscriberCellularProviders?.first?.value
if let carrierName = carrier?.carrierName {
cell.textLabel?.text = carrierName
}
else{
cell.textLabel?.text = "No Data"
}
//-------------------------------------------------------
Output : Carrier
Solution 2 : When I tried this solution I've received "iPhone X" as output, instead of CarrierName.
Solution 2 :
//--------------- CodeBase : Solution2 -----------------
let networkInfo = CTTelephonyNetworkInfo()
let carrier = networkInfo.serviceSubscriberCellularProviders?.first?.value
if var carrierName = carrier?.carrierName {
if carrierName.contains("Carrier"){
carrierName = self.getCarrierName() ?? "No Data"
}
else{
cell.textLabel?.text = carrierName
}
}
else{
cell.textLabel?.text = "No Data"
}
//-------------------------------------------------------
Func : getCarrierName()
//--------------- CodeBase : Part of Solution2 -----------------
func getCarrierName() -> String? {
var carrierName: String?
let typeName: (Any) -> String = { String(describing: type(of: $0)) }
let statusBar = UIApplication.shared.value(forKey: "_statusBar") as! UIView
for statusBarForegroundView in statusBar.subviews {
if typeName(statusBarForegroundView) == "UIStatusBarForegroundView" {
for statusBarItem in statusBarForegroundView.subviews {
if typeName(statusBarItem) == "UIStatusBarServiceItemView" {
carrierName = (statusBarItem.value(forKey: "_serviceString") as! String)
}
}
}
}
return carrierName
}
//-------------------------------------------------------
Output : iPhone X i.e. It returns ModelName instead of CarrierName
Can someone, please help me to get - CarrierName.
In my case (Xcode 11.4, Swift 5.2, iPhone 8, iOS 13.3.1), I can get the proper carrier name.
Code snippet:
import CoreTelephony
if #available(iOS 12.0, *) {
if let providers = CTTelephonyNetworkInfo().serviceSubscriberCellularProviders {
providers.forEach { (key, value) in
print("key: \(key), carrier: \(value.carrierName ?? "nil")")
}
}
} else {
let provider = CTTelephonyNetworkInfo().subscriberCellularProvider
print("carrier: \(provider?.carrierName ?? "nil")")
}
The result in console is:
key: 0000000100000001, carrier: 中国电信
In my 'Swift' app I have a feature of uploading photos to my Amazon S3 bucket. When the user is connected to WiFi or LTE, there's no problem, but when the connection is a little slower (e.g. 3G), then the upload takes a lot of time (up to one minute) and iphone can lose 15-20% of battery! I resize photos down to around 200-300kb, so that should not be a problem. The code that I use for that is:
func awsS3PhotoUploader(_ ext: String, pathToFile: String, contentType: String, automaticUpload: Bool){
let credentialsProvider = AWSCognitoCredentialsProvider(regionType:CognitoRegionType,
identityPoolId:CognitoIdentityPoolId)
let configuration = AWSServiceConfiguration(region:CognitoRegionType, credentialsProvider:credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
let uploadRequest = AWSS3TransferManagerUploadRequest()
uploadRequest?.body = URL(string: "file://"+pathToFile)
uploadRequest?.key = ProcessInfo.processInfo.globallyUniqueString + "." + ext
uploadRequest?.bucket = S3BucketName
uploadRequest?.contentType = contentType + ext
uploadRequest?.uploadProgress = { (bytesSent, totalBytesSent, totalBytesExpectedToSend) -> Void in
DispatchQueue.main.async(execute: { () -> Void in
if totalBytesExpectedToSend > 1 {
print(totalBytesSent)
print(totalBytesExpectedToSend)
}
})
}
let transferManager = AWSS3TransferManager.default()
transferManager?.upload(uploadRequest).continue({ (task) -> AnyObject! in
if (task.isCompleted) {
print("task completed")
}
if let error = task.error {
print("Upload failed ❌ (\(error))")
}
if let exception = task.exception {
print("Upload failed ❌ (\(exception))")
}
if task.result != nil {
let s3URL: String = "https://myAlias.cloudfront.net/\((uploadRequest?.key!)!)"
print("Uploaded to:\n\(s3URL)")
}
else {
print("Unexpected empty result.")
}
return nil
}
)
}
Is there anything that comes up to your mind of what am I doing wrong here and how could this huge battery consumption be avoided?
The following answer was inspired by https://stackoverflow.com/a/20690088/3549695
I believed what you need to do is the ability to detect the Type of the Radio Network. Be it WiFi, LTE, 3G, 2G, or No Network.
Then the app will need to make the decision, based on the result.
I created a test Xcode project to test this concept on my iPhone 6.
It seems to work, but I could only test 'Air Plane Mode', WiFi and LTE. I can't get myself into 2G or 3G network.
In case of either WiFi or LTE, I will get the value:
'CTRadioAccessTechnologyLTE'
While in 'Air Plane Mode', the Optional value will be nil. So it's up to me what text I replace it with. And I choose to output 'Not able to detect'
Here is what my ViewController.swift looks like:
import UIKit
import CoreTelephony
class ViewController: UIViewController {
#IBOutlet weak var currentRAN: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func detect(_ sender: UIButton) {
if case let telephonyInfo = CTTelephonyNetworkInfo(),
let currentRadioAccessTech = telephonyInfo.currentRadioAccessTechnology {
currentRAN.text = currentRadioAccessTech
print("Current Radio Access Technology: \(currentRadioAccessTech)")
} else {
currentRAN.text = "Not able to detect"
print("Not able to detect")
}
}
}
Where the possible values for .currentRadioAccessTechnology are:
/*
* Radio Access Technology values
*/
#available(iOS 7.0, *)
public let CTRadioAccessTechnologyGPRS: String
#available(iOS 7.0, *)
public let CTRadioAccessTechnologyEdge: String
#available(iOS 7.0, *)
public let CTRadioAccessTechnologyWCDMA: String
#available(iOS 7.0, *)
public let CTRadioAccessTechnologyHSDPA: String
#available(iOS 7.0, *)
public let CTRadioAccessTechnologyHSUPA: String
#available(iOS 7.0, *)
public let CTRadioAccessTechnologyCDMA1x: String
#available(iOS 7.0, *)
public let CTRadioAccessTechnologyCDMAEVDORev0: String
#available(iOS 7.0, *)
public let CTRadioAccessTechnologyCDMAEVDORevA: String
#available(iOS 7.0, *)
public let CTRadioAccessTechnologyCDMAEVDORevB: String
#available(iOS 7.0, *)
public let CTRadioAccessTechnologyeHRPD: String
#available(iOS 7.0, *)
public let CTRadioAccessTechnologyLTE: String
I am using the new version of retrieving contacts from the Phonebook on iOS. There is a problem with the unique identifier, which is returned by the first query. Normally the unique identifier is a UUID, but sometimes it is appended by “: ABPerson”.
Querying the Phonebook API with this identifier works sometimes, but not always. Does anybody know, if there is a way to avoid this behaviour?
Currently, I try with a safeguard of two queries. The first is using the identifier as is, the second (if the first fails) is to use the identifier stripping the “: ABPerson” extension.
The first query used is:
class func getAllPhoneBookContacts() -> [PhonebookContact] {
let contactStore = CNContactStore()
var contacts = [PhonebookContact]()
PhoneBookContactsHelper.requestForAccess { (accessGranted) -> Void in
if accessGranted {
let keys = [CNContactIdentifierKey, CNContactPhoneNumbersKey]
do {
let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
try contactStore.enumerateContactsWithFetchRequest(fetchRequest, usingBlock: { (contact: CNContact, _) -> Void in
for phoneNoLab in contact.phoneNumbers {
if let phoneNo = phoneNoLab.value as? CNPhoneNumber,
normalizedPhoneNumber = PhoneNumberNormalizer.normalizePhoneNumber(phoneNo.stringValue) {
let pbc = PhonebookContact(contactID: contact.identifier, phoneNumber: normalizedPhoneNumber)
contacts.append(pbc)
}
}
})
}
catch {
NSLog("Unable to fetch contacts.")
}
}
}
return contacts
}
Accessing a certain contact again later by the identifier is done by:
class func getContactNameByUUID(identifier: String) -> String?{
var name : String?
PhoneBookContactsHelper.requestForAccess()
{ (accessGranted) -> Void in
if accessGranted {
let contactStore = CNContactStore()
let keys = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName)]
do {
let cnc = try contactStore.unifiedContactWithIdentifier(identifier, keysToFetch: keys)
name = CNContactFormatter.stringFromContact(cnc, style: .FullName)!
}
catch _ {
NSLog("Could not fetch contact with id \(identifier))")
}
}
}
return name
}
I am using iOS 9 and tested on simulator and various iPhones and the unexpected behaviour is present everywhere.
My crashlog is here.
This crash occurs on some of the testers. One tester with iPhone 6 (iOS 8.4.1) crashes but another tester with the same device (also iOS 8.4.1) doesn't crash. It also works on simulator.
I know this has been asked before but I think the problem is about ABPeoplePickerNavigationController. Or maybe this is a problem about Crashlytics.
What I wonder is: why do the same devices with the same operating system works differently? Also, I appreciate any solution to this problem?
Here is my code:
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!, property: ABPropertyID, identifier: ABMultiValueIdentifier) {
if property != kABPersonPhoneProperty {
return
}
let phoneNumbers: ABMultiValueRef = ABRecordCopyValue(person, kABPersonPhoneProperty).takeRetainedValue()
if (ABMultiValueGetCount(phoneNumbers) > 0) {
let index = ABMultiValueGetIndexForIdentifier(phoneNumbers, identifier)
let selectedPhoneNumber = ABMultiValueCopyValueAtIndex(phoneNumbers, index).takeRetainedValue() as! String
let newNumber = "tel:\(phoneNumber)"
println(newNumber)
let url = NSURL(string: newNumber)
if UIApplication.sharedApplication().canOpenURL(url!) {
//test
//UIApplication.sharedApplication().openURL(url!)
} else {
return
}
} else {
return
}
}