Archive for the ‘iOS’ Category.

Background thread URL download in Swift 4

I was recently working on a project in swift, and was not able to quickly find a way to use a background thread to do a URL download in Swift 4. There were plenty of snippets and tutorials out there, but they were all for older versions of Swift. (BTW Swift, you have to stop it with these breaking changes.)

So here is what I cobbled together to download from the OpenAQ web site, which has an open API to provide air quality data.

The Swift class that does the downloading and processing of the data from the site is this class, which I called OpenAQ.swift:

//
//  OpenAQ.swift
//  openaq
//
//  Created by Brian Prescott on 11/16/17.
//  Copyright © 2017 Brian Prescott. All rights reserved.
//
 
import UIKit
 
class OpenAQResult: NSObject {
    var city: String = ""
    var count: Int = 0
    var country: String = ""
    var locations: Int = 0
}
 
protocol OpenAQDelegate: class {
    func didFinish(sender: Any)
    func didError(sender: Any)
}
 
class OpenAQ: NSObject {
 
    weak var delegate:OpenAQDelegate?
    var returnValue: [OpenAQResult] = []
 
    func readDataBackground() -> Void {
        let urlString = "https://api.openaq.org/v1/cities?limit=10000"
        guard let url = URL(string: urlString) else { return }
        URLSession.shared.dataTask(with: url) { (data, res, err) in
 
            guard let data = data else {
                return
            }
 
            do {
 
                let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
                let resultsArray = json["results"] as! NSArray
                var retval: [OpenAQResult] = []
                for x in resultsArray {
                    let dict = x as! NSDictionary
                    let res = OpenAQResult()
                    res.city = dict.value(forKey: "city") as! String
                    res.count = dict.value(forKey: "count") as! Int
                    res.country = dict.value(forKey: "country") as! String
                    res.locations = dict.value(forKey: "locations") as! Int
                    if (res.count >= 10000) {
                        retval.append(res)
                    }
                }
                self.returnValue = retval.sorted(by: { $0.count > $1.count })
                self.delegate?.didFinish(sender: self)
            } catch {
                print("An error occurred")
            }
 
        }.resume()
    }
 
}

So then to use this class, here is a template of what your main view controller (or wherever you want to use it) would look like:

class MasterViewController: UITableViewController {
 
    var detailViewController: DetailViewController? = nil
    var objects = [OpenAQResult]()
    var openAQ: OpenAQ? = nil
 
    override func viewDidLoad() {
        super.viewDidLoad()
 
        openAQ = OpenAQ()
        openAQ?.delegate = self
        openAQ?.readDataBackground()
 
        // do the rest of your initialization
    }
 
    // the rest of your class would go here
 
}
 
extension MasterViewController: OpenAQDelegate {
    func didError(sender: Any) {
        print ("An error occurred!!!")
    }
 
    func didFinish(sender: Any) {
        objects = (sender as! OpenAQ).returnValue
        if (objects.count == 0) {
            let alert = UIAlertController(title: "Error", message: "The data could not be loaded. Please establish an internet connection and press the Refresh button above.", preferredStyle: UIAlertControllerStyle.alert)
            alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
 
        DispatchQueue.main.async {
            // do something here
            // for example, reload a table view
        }
    }
}

In the code above, when the didFinish method is called, the objects instance variable is retrieved from the openAQ variable, and you can then do something with the data you just retrieved.

BTW, Happy Thanksgiving to all my U.S. readers. It has been a very trying year personally and all around, but we still have so much to be thankful for.

Watch out for #ifdef TARGET_IPHONE_SIMULATOR

I just wanted to take a moment before the sands of time in the April hourglass run out for a friendly reminder for those iOS developers who are working in older Objective-C code bases. Watch out for #ifdef TARGET_IPHONE_SIMULATOR, as at some point, a newer version of the iOS SDK broke this check. In the current TargetConditionals.h file, TARGET_IPHONE_SIMULATOR resolves to either a 0 or a 1 (eventually, see below), which means that no matter where you run the code, TARGET_IPHONE_SIMULATOR will be defined, hence the code inside the #ifdef will always run.

The solution is of course to use the following line instead:

#if TARGET_IPHONE_SIMULATOR

Keep in mind that TARGET_IPHONE_SIMULATOR itself is marked as deprecated in the iOS 10 SDK TargetConditionals.h file, so I suppose to be future-proof, you should instead use:

#if TARGET_OS_SIMULATOR

BTW, Happy Honesty Day to my U.S. readers. All you need to know about Honesty Day is that I think it is one of the best days on the calendar.

Find the bundle ID of an app in the App Store

Have you ever had to find the bundle ID of an app in the App Store? Well if you have, there does not seem to be a direct way to do it, so here is how I found the information.

First, use the iTunes Link Maker to search for iOS apps, find the app you need to discover the bundle ID for, and click on it. This will pull up a screen with an embed code link, find the numeric ID code.

Then, visit this URL, replacing the X’s with the numeric ID you just found:

https://itunes.apple.com/lookup?id=XXXXXXXXX

After you plug in your numeric ID, the Apple site should spit back a JSON file in response, look for the bundleId entry.

BTW, I am a day late, but Happy 40th Anniversary to Slap Shot, one of the finest cinematic masterpieces of all time.

Eliminate noise from the iOS Simulator console

If you find that you are getting a lot more stuff showing up on the console when debugging your app in the Simulator, you are not alone. Luckily, there is a way to eliminate noise from the iOS Simulator console output.

To do this, go into the scheme editor for your target, select the Run Debug option on the left, select the Arguments tab on the right, and under Environment Variables, click the + button to add a new one called OS_ACTIVITY_MODE, and set its value to false.

BTW, a posthumous Happy Birthday to the King, Elvis Presley.

Ignore warnings in a specific file in Xcode

If you use any 3rd party or open source code in your iOS or macOS application, you know how deflating it is to get your code all compiling with no errors or warnings, only to have your open source code spit out lots of warnings. If only there were a way to ignore warnings in a specific file in Xcode…

Well as it turns out, you can go into the Build Phases section in your target’s settings, find the file or files you want to disable warnings for, double click on the Compiler Flags, and add in the following entry:

-w -Xanalyzer -analyzer-disable-all-checks

This entry will have the additional benefit of also disabling the Clang static code analysis warning messages for the files, which can be just as annoying as the warnings.

BTW, Happy 60th Birthday to the Wizard, Jordan Rudess of Dream Theater. Jennifer and I saw them here in Columbus tonight as they performed The Astonishing live, and the show was great.

METHOD_LOG

I have a lot more projects that I am working on at my new gig, and as such, I am always looking for my METHOD_LOG definition. It is a quick define that I put in my Objective-C code so that I can log what class and method I am in, without having to type out a description of the class and method into an NSLog statement.

So here is the code for METHOD_LOG, along with a DebugLog define that I use quite often instead of NSLog:

#if defined(DEBUG)
    #define METHOD_LOG (NSLog(@"METHOD_LOG: %@ %s", NSStringFromSelector(_cmd), __FILE__))
    #define DebugLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
    #define METHOD_LOG
    #define DebugLog(...)
#endif

BTW, Happy International Caps Lock Day.

Codesign error: “Warning: –resource-rules has been deprecated in Mac OS X >= 10.10”

So you are humming along and everything is going great. Then all of a sudden you start getting the error message “Warning: –resource-rules has been deprecated in Mac OS X >= 10.10”, especially if you are using some kind of tool to re-sign code.

Apparently, in your build settings for your target in Xcode, there is a Code Signing Resource Rules Path entry that is no longer supported. Once this is removed and the app is rebuilt, the message should no longer appear. Of course, if you are re-signing an app that you do not have the source code for, this could be a challenge.

Here is the technical note from Apple that describes the issue:

TN2206

BTW, Happy 60th Birthday to the best rock guitar player of all time, Joe Satriani. I am looking forward to seeing Joe in a few weeks at the G4 Experience on Long Island.

Calling a .NET web service endpoint with NSURLSessionUploadTask

I have been going through the process of removing open source components from my apps when Cocoa Touch and the associated frameworks catch up with what I need to do.

Let’s say for the sake of argument you have a .NET web service that looks like this:

using System;
using System.Web.Script.Services;
using System.Web.Services;
 
namespace My_WebRole
{
    /// <summary>
    /// Summary description for CreateCustomer
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    [ScriptService]
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
    // [System.Web.Script.Services.ScriptService]
    public class CreateCustomer : WebService
    {
        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public string Create()
        {
            try
            {
                var myName = Context.Request.Form["MyName"];
                var companyName = Context.Request.Form["CompanyName"];
                var db = new MyServicesDataContext();
                var row = new CustomerInformation { MyName = myName, CompanyName = companyName, CreationDate = DateTime.Now };
                db.CustomerInformations.InsertOnSubmit(row);
                db.SubmitChanges();
            }
            catch (Exception ex)
            {
                return ex.ToString();
            }
            return "OK";
        }
    }
}

The NSURLSessionUploadTask class was added in iOS 7, which is sufficiently long enough ago to be stable to implement. (As long as your app is going to require iOS 7 or newer, of course.)

Here is what the code looks like that calls the above web service:

+ (BOOL)pushTheData:(NSString *)myName companyName:(NSString *)companyName
{
    NSMutableString *urlString = [NSMutableString stringWithString:PUSH_DATA_URL];
    NSString *boundary = [NSString boundaryString];
    NSMutableData *body = [NSMutableData data];
 
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", @"MyName"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", myName] dataUsingEncoding:NSUTF8StringEncoding]];
 
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", @"CompanyName"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", companyName] dataUsingEncoding:NSUTF8StringEncoding]];
 
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
 
    NSURL *url = [NSURL URLWithString:urlString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"POST"];
    [request addValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
 
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    configuration.HTTPAdditionalHeaders = @ { @"Content-Type"  : [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] };
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
 
    NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error)
        {
            // handle the error
            return;
        }
 
        NSLog(@"Response: %@", [data utf8String]);
        // do something here
    }];
 
    [task resume];
 
    return YES;
}

The above method uses a category that I have set up off of NSString that creates a new boundary string each time it is called. Here is that method:

+ (NSString *)boundaryString
{
    NSString *uuidStr = [[NSUUID UUID] UUIDString];
    return [NSString stringWithFormat:@"Boundary-%@", uuidStr];
}

BTW, Happy Birthday to Jeff Beck. He has a new album coming out soon, and I can’t wait to hear it.

Xcode project line counts

If you are looking to find out how many lines of code are in each of the files of your Xcode project, you can use the following command in Terminal after you change into the root folder of your project:

find . \( -iname \*.m -o -iname \*.mm -o -iname \*.h \) -exec wc -l '{}' \+

SpriteKit game background music stopped working

In my SpriteKit game, all of a sudden the background music stopped playing, all I heard was silence no matter what the volume levels were in either the app settings or via the hardware buttons.

I am not sure why this happened, but I was able to figure out how to get it working again. The key was adding an AVAudioSession call before the existing AVAudioPlayer code. Here is some sample code of what I did:

AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
 
NSURL *url = [NSURL fileURLWithPath:@"your_sound_file.caf"];
_player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[_player setDelegate:self];
[_player prepareToPlay];
[_player play];

BTW, Happy Birthday to Brutus Beefcake. (I couldn’t really find any other good birthdays, deaths, or events in the Wikipedia page for today.)