Category Archives: coding

Adventures in app writing and publishing

Your icon can’t look like an iMac

After about 10 updates with my new icon for Multi Monitor Wallpaper, Apple decided to reject an update because

Guideline 5.2.5 – Legal

Your app does not comply with the Guidelines for Using Apple’s Trademarks and Copyrights. Specifically, your app includes:

– Apple trademark, iMac profile/ image, imagery – or likeness – in the icon

This icon is not OK any more:

This icon is fine (for now)

Notice the narrower bezel and narrower darker stand. Apparently that makes all the difference 🙂

To be clear – the icon clearly was inspired by the iMac, though it is custom built by a third party, and not based on Apple artwork.

Anyway Multi Monitor Wallpaper has a new icon.

Active Storage: Adding a custom Analyzer for unsupported image type

When you add an attachment with Active Storage, Rails kicks off a background job to analyse the attachment. You can add your own analyzer – but the documentation is very thin on the details.

One of my apps uploads .heic image files. Unfortunately as of Rails 5; this triggers a crash in the image analyzer because imageMagick can’t yet handle this filetype.

My solution to the crash is to create a custom analyzer that handles image/heic files, and returns no metadata.

# lib/models/heic_analyzer.rb

class HeicAnalyzer < ActiveStorage::Analyzer

	def self.accept?(blob)
	  blob.content_type == "image/heic"
	end

	def metadata
	  {}
	end

end

Make sure the class is loaded

# application.rb

config.autoload_paths << "#{Rails.root}/lib/models"

then add the Heic analyzer to active storage

# config/initializers/image_analyzer.rb

Rails.application.config.active_storage.analyzers.prepend HeicAnalyzer

That’s it. Rails now handles my .heic attachment without crashing

ITunes: You’re doing it wrong. I could help – but I won’t.

I just tried to redeem a code for a Mac app through iTunes.

Apple identify that I’m using the code in the wrong place and tell me what to do (use the Mac App Store), but they could easily do better.

Better: Instead of a red error message – why not give me a link that opens the Mac App Store with the code pre-filled?

According to Stack Overflow, this link used to work:
macappstores://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemLandingPage?code=NHWEL3FHRYKR

As of April 23rd 2019, that link opens the right page – but doesn’t pre-fill the code. (I have filed a bug)

Best: Why not just redeem the code for me? I’m already logged in on my mac with the right account.

Apple auto-updates Xcode, but you can’t submit with it

I woke up this morning to find that my Mac auto-updating from Xcode 10.1 to Xcode 10.2

Amongst other things, this disables Swift 3, and introduces Swift 5.

So – after updating a fork of one of the libraries I use, I updated my own code to Swift 5 and submitted to the App Store. Only to find the following:

The newly released version of XCode can’t be used to submit apps.

It turns out this is standard practice from Apple

This isn’t good enough. This kind of thing needs a checklist:

Xcode Release Checklist

  • Has it been tested
  • Can developers use it
  • Really? Can developers submit builds with it?
  • Even Mac Apps?

It’s fine to release beta versions that we can’t use.

It’s not cool if release versions don’t work.

Is It Working will stay free for some time…

I started Is It Working as a ‘side project’.

(Is It Working checks that your SSL certificates are not expiring, and that your background server processes are running as expected)

I admit – I had hopes that it would be a huge instant success with hundreds or even thousands of users – and I’d be instantly rich 🙂

via Giphy

Sadly this didn’t happen. Folks are using IsItWorking – but the numbers are not huge.

On a related note, EU tax law has an annoying ‘feature’ where if you sell a single pound worth of digital services, you need to register for sales tax and send reports every three months.

I’m allergic to that kind of admin.

So, given that I don’t want to do the admin, and that it would be a chunk of work to add a payment system to Is It Working – I’m not going to.

This is not to say that I’ll never charge – but if I do start charging, I’ll give reasonable notice, and I won’t charge current users more than $1 / month for 10 checks.

This also means that I’m not doing significant work on Is It Working.  I’ll keep it running because

  • I think it is cool
  • I use it for my own purposes

Of course – if you have a feature you’d like to see, I’m happy to add features on a sponsored basis.
I hope Is It Working will still be useful for people. If you like it – please tell your friends.
Perhaps I’ll still be rich some day 🙂

HSNotifications – Easier Better Notifications in Swift

Introducing HSNotifications. A simple, sensible, easy-to-use wrapper around NSNotificationCenter

Let’s jump straight in. I mostly use notifications to keep my UI up to date. That means ViewControllers end up with a bunch of observers that need to be activated and de-activated with the ViewController lifecycle

Easy ViewController Integration

class ViewController: NSViewController, HSHasObservers {
    
    override func viewDidLoad() {
        super.viewDidLoad()

        //Create and add
        HSObserver.init(forName: Foo.didAThing,
                            using: { (notif) in
                                //Do Something
        }).add(to: self)
        
        //Monitor multiple notifications
        HSObserver.init(forNames: [Bar.oneThing,Bar.anotherThing] ,
                            activate:true,
                            using: { (notif) in
                                //Do Something Else
        }).add(to: self)
    }
    
    override func viewWillAppear() {
        super.viewWillAppear()
        
        activateObservers()
    }
    
    override func viewDidDisappear() {
        super.viewDidDisappear()
        
        deactivateObservers()
    }
}

Taking this step by step:

ViewController uses the protocol HSHasObservers

This allows us to add observers to the ViewController (they are stored in the observers array)

Next we create an HSObserver object. The block will be triggered by any Foo.didAThing notification.

We take sensible defaults

  • Assume you want to use NSNotificationCenter.default
  • Assume you want your block to fire on the main queue
  • Assume you don’t care what object broadcasts the notification.

(Of course – you can override any of these if you want to.)

The observer is added to the ViewController with .add(to:self)

Next we add an HSObserver to observe multiple Notifications

Finally, the observers are activated and deactivated in viewWillAppear and viewDidDisappear

When the ViewController is released, they are cleaned up automatically.

Standalone Observers

    var waveObserver:HSObserver
    init() {
        waveObserver = HSObserver.init(forName: Watcher.wave,
                                           using: { (notif) in
            //Do Something
        })
        
        //activate
        waveObserver.activate()
        
        //deactivate
        waveObserver.deactivate()
    }

HSObserver objects can be stored as a standard variable.

This allows them to be activated and deactivated.

They clean up properly when they are released with the owning object.

There are of course lots of options available here.

    /// Create observer
    ///
    /// - parameter name:  notification name
    /// - parameter obj:   object to observe (default nil)
    /// - parameter queue: queue to run the block on (default main)
    /// - parameter center: notification center (default NotificationCenter.default)
    /// - parameter block: block to run (beware of retain cycles!)
    ///
    /// - returns: unactivated manager. Call activate() to start
    convenience init(forName name: NSNotification.Name, 
                     object obj: Any? = nil,
                     queue: OperationQueue? = .main,
                     center newCenter: NotificationCenter = NotificationCenter.default,
                     activate: Bool = false,
                     using block: @escaping (Notification) -> Swift.Void)

Get HSNotification at GitHub

Secret Mac App Store Rule – Minimum Trial Length

I recently started experimenting with free trials in the Mac App Store.

Photo by AbsolutVision on Unsplash

The first App I changed is Icon Tool. This is an incredibly simple app that lets developers generate icon assets for iOS or Mac OS apps.

Because the app is so simple, I only wanted to give a short trial. Just long enough for you to see how it works. The relevant app store rule is:

3.1.1  In-App Purchase:

Non-subscription apps may offer a free time-based trial period before presenting a full unlock option by setting up a Non-Consumable IAP item at Price Tier 0 that follows the naming convention: “XX-day Trial.”

So, I created a 1-day Trial IAP item and submitted my app. It was rejected with the reason:

‘We found that your app includes an in-app purchase free trial period, but the free trial period is shorter than the minimum 3 days.’

I have asked for clarification on where/if the 3 day rule is documented and got the response:

‘We understand that you may not agree with the feedback we have provided. However, to ensure App Store customers a safe and enjoyable experience, all apps must comply with the App Store Review Guidelines.’

I have no argument with this statement. My issue is that the 3 day rule doesn’t exist in the published guidelines

I submitted an appeal essentially asking for clarification on whether this was really a rule, and if so – where/whether it was published.

The appeal responded with a section of text which did indeed describe the 3-day minimum. That text isn’t in the published guidelines though (and frustratingly, I didn’t save it).

So – It’s a rule. A secret rule, but one I have to follow.

Google Play – No need to go Nuclear!

One of the apps I maintain recently got this message from Google Play

After review, <your app> has been removed from Google Play due to a policy violation. This app won’t be available to users until you submit a compliant update.

Issue: Violation of Usage of Android Advertising ID policy and section 4.8 of the Developer Distribution Agreement

The app was completely removed from the store.

It’s a fair cop. We use Google’s firebase library, and we hadn’t realised that this library uses the advertising ID.

The fix was simple. We had to update the privacy policy, make sure that the store listing points to the privacy policy, and add a link in the settings of the app which opens the privacy policy. In other words – as far as users are concerned, nothing much changed.

I have no objection to the rule – but removing the app from the store is massive overkill.

Apple deal with this kind of issue with a message to the developer that then need to submit an update within (say) 7 days. Surely Google could adopt a similar approach for minor violations.

Boofing Supreme – Rejected!

Coming Soon

Boofing Supreme

Sound Credits: Mike Koenig, http://soundbible.com/

Update: Apple was not amused. Boofing Supreme was rejected…

Supreme court judge Brett Kavanaugh testified that that the ‘boofing’ referred to in his yearbook referred to flatulence – so I’d hoped this might get through on the basis of sworn testimony, or satire…

Groundhog Day: Google Play keeps removing apps for the same false reasons.

Google requires that apps send sensitive information over https, and it also requires that you prominently disclose what you’re doing with user data.

This ‘a good thing’. I fully support these requirements.

Unfortunately, Google keeps removing my apps from the store for breaking these rules when I 100% definitively do not.

All my Android apps offer you an option to get setup instructions sent to you by email. This is the process:

1) I offer to send you instructions. You click on the orange button ‘Yes Send me details’

2) I open up a new page where you enter your email address and click the orange button ‘Send’

You won’t be surprised that this sends your email address to my server where I use it to send you instructions.

Naturally – that data is sent over https.

Here is where it gets frustrating:

First Removal

17th May:Message from Google Play:

After review, VLC Remote, com.hobbyistsoftware.android.vlcremote_us, has been removed from Google Play because it violates our personal and sensitive information policy.

…must handle the user data securely,… (for example, over HTTPS)….Your app is not currently handling user data securely.

I wrote back to explain that yes – my app is using https. They respond to say that ‘If, after making changes, you think your app is in compliance, please submit your app for another review.’

I clarify that I’m not making any changes – because none are required and resubmit.

25th May: They respond to say

Good news – I see your app, VLC Remote (com.hobbyistsoftware.android.vlcremote_us), was resubmitted earlier and has been approved.

Second Removal

29th May: A Very Similar Message

After review, VLC Remote, … has been removed from Google Play because it violates our personal and sensitive information policy … This app won’t be available to users until you submit a compliant update.

We go through the same dance. I explain that it isn’t violating their policy. It does send the user’s email to my server, but only when explicitly asked to – and over https.

They approve the resubmission.

Seriously – Again???

6th June:

After review, VLC Remote, com.hobbyistsoftware.android.vlcremote_us (Version Code: 47963), has been removed from Google Play because it violates our personal and sensitive information policy

The focus now is on ‘Prior to the collection and transmission, it must prominently highlight how the user data will be used, describe the type of data being collected and have the user provide affirmative consent for such use.’

I’m lost for words here. Supposedly, someone has reviewed the app. And they have looked setup help process. They clicked on ‘yes send me details’ then entered their email address, clicked ‘Send’ and they consider that I haven’t been clear about what is going on.

Incidentally – I had the exact same process with VLC Streamer on 20th March.

And of course my app is off the store – and not making any sales

I’ll build again, submit again and see what happens. This is getting very boring though…

Removed again – despite already being removed, and not having resubmitted yet!

13th June:

After review, VLC Remote, … has been removed from Google Play because it violates our personal and sensitive information policy

Your app is uploading users’ email information to … without posting a privacy policy in both the designated field in the Play Developer Console and from within the Play distributed app itself. Your app must also handle user data securely, … (for example, over HTTPS).

I’m guessing that my ‘this is ridiculous’ email triggered a review.

This time, the primary objection is that the app must have ‘a privacy policy in both the designated field in the Play Developer Console and from within the Play distributed app itself’

Of course – I do.

I know this because on the 29th May, VLC Remote was removed for exactly that reason.
On the 29th of May – it was correct. I did have a privacy policy linked in the store – but it wasn’t within the app itself. This app has been up for years, and I don’t know when the requirement came in to have the privacy policy within the app.

Anyway – after the email of the 29th of May, I sent in an update which added the privacy policy into the app within the settings page. I resubmitted and was approved.

At the time, I thought it was ridiculous that Google would remove the app from the store immediately over a violation like this. They could easily have sent me an email and given me (say) 7 days to put things right.

It was more ridiculous than removing me for the same reason _after_ I have fixed the problem.

btw; In the Apple store, when you submit an app for review, there is a ‘reviewer notes’ field. You can use this to let the reviewer know anything you think is important.

For example you might let them know where the privacy policy was shown (in the settings) – or that you always upload sensitive data over https. Google has nothing like this, and clearly doesn’t keep notes on review decisions / appeals.

I’m going to respond to the latest email with a link to this blog post. We’ll see what happens.

Update 18th June

I submitted yet another appeal. This time with a google doc to explain what was going on.

The response looked like the old rejections – but in the reviewer did helpfully highlight the version number.

It turns out this is an old build uploaded in 2016 which targets users on API level 14. There are 20 active installs.

I disabled all the old versions of the app, resubmitted, and (so far) everything is live.

(Hopefully) Final Thoughts

The Apple review process can be infuriating. Apple have some rules that seem ridiculous to me, and they’re seldom flexible about applying them. However, they communicate clearly – as real people. When there is a minor issue, they’ll ask you to fix it rather than just booting you out of the store. They also seem able to keep notes about previous discussions and if they have resolved one issue – they won’t come back to it again.

By contrast – Google make it seem like you’re dealing with a badly programmed Eliza-Bot.

There is no context in their replies – they just bang out the same template letters with no acknowledgement of any points, requests or comments you have made.

I’m wondering if they are forbidden from sending out personalised emails – and have to resort to highlighting as the only marginally personal communication method available to them.

I never got any acknowledgement that the first four removal reasons were completely without merit. But my app spent weeks out of the store because of them.

The final rejection felt like they were scraping the barrel to justify the removal – and although it was probably true (I haven’t checked) – the particular version was only used by 20 devices, and had been in place since 2016, so they could have either disabled that one version, or approved the app and asked me to do that within (say) 7 days.

This whole process was deeply frustrating – and in the end, no changes have been made to the builds which triggered the first bunch of rejections.