Updates – Cocoanetics https://www.cocoanetics.com Our DNA is written in Swift Fri, 30 Sep 2022 20:04:48 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.3 39982308 DTCoreText 1.6.27 https://www.cocoanetics.com/2022/09/dtcoretext-1-6-27/ https://www.cocoanetics.com/2022/09/dtcoretext-1-6-27/#comments Fri, 30 Sep 2022 20:04:47 +0000 https://www.cocoanetics.com/?p=10738 This is a maintenance release, after the previous one was more than a year old. There were a couple open pull requests which I merged.

Changes

  • Changed DTTextAttachment to be a subclass of NSTextAttachment to avoid some crashes.
  • Remove the checking of tiled layer in DTAttributedTextContentView.
  • Removed unneeded constant causing a warning
  • Added support for underline color
  • Added ability to pass in a UIFontDescriptor for DTHTMLAttributedStringBuilder

On the first change I think there might be some further work necessary to actually use the native sizing functions correctly. DTCoreText was started at a time when there was no NSTextAttachment on iOS (before iOS 7). Since we cannot even build for this platform any more for a few years, it was ok to make this change.

The last change got implemented to deal with an issue where Times New Roman would be used instead of San Francisco UI.

Many thanks to the contributors of pull requests!

]]>
https://www.cocoanetics.com/2022/09/dtcoretext-1-6-27/feed/ 10 10738
Accessibility in SpeakerClock 1.3.1 https://www.cocoanetics.com/2021/07/accessibility-in-speakerclock-1-3-1/ https://www.cocoanetics.com/2021/07/accessibility-in-speakerclock-1-3-1/#comments Tue, 20 Jul 2021 19:56:50 +0000 https://www.cocoanetics.com/?p=10725 You can now fully operate SpeakerClock with no or low vision. We gave SpeakerClock the full Accessibility treatment. In this article I describe some of the things I learned adding accessibility features to SpeakerClock, now that it is fully written in SwiftUI.

SpeakerClock’s UI is divided into 3 sections, the presets at the top, the LED timer as big as possible in the center and the phase “traffic light” at the bottom.

The main interaction gesture is to horizontally swipe over the LED digits to change the timer. This kind of gesture is not possible when VoiceOver is active because there you can pan over the screen to quickly get to the hot spots of interactive elements.

I solved this by having individually adjustable sub-elements for the minutes and seconds while the timer is not running. This way you can swipe vertically to adjust the minutes and seconds separately.

There are three kinds of standard gestures for Voice-Over which I made full use of:

  1. double-tap with single finger to select an element
  2. double-tap with two fingers to perform a “magic tap”
  3. draw a Z with two fingers to “escape”

I used #1 for the single-tap function of the preset and speaking phase buttons. #2 substitutes for the long press. The rationale is that you have to consciously tap with two fingers instead of one to modify the presets, as to prevent you from accidentally changing them.

In the regular flow of things, VoiceOver basically comments on the focused element and after a short pause also reads out the accessibility hint that tells the user what interactions are possible. I also used VoiceOver’s announcement notifications to give audio feedback on some interactions.

The cherry on top is that certain timer values get red out aloud. In the yellow and green phases you get a voice prompt every minute. The phase transitions get announced as well. In the red phase there is an announcement very 15 seconds, with the final 10 seconds being accompanied by beeps.

That felt like a reasonable amount of voice feedback for starters. I might add some configuration options at a later point.

In this video I am demonstrating all that we discussed.

Conclusion

I would say my implementation is 95% perfect. There are some edge cases still – which I cannot do much about – where VoiceOver will insist of speaking something that it wouldn’t need to. Unfortunately there is no way to tell Accessibility to “shut it” for certain times when there is something more important going on.

It cost me a lot of experimenting and the better part of a day to get to this stage. I am anxious to hear from actual users of SpeakerClock, in particular those who are visually impaired and might have use for a timer. And some regular users also asked about acoustic feedback. What sort of configuration options related to sounds might make sense?

]]>
https://www.cocoanetics.com/2021/07/accessibility-in-speakerclock-1-3-1/feed/ 2 10725
SpeakerClock 1.3.0 https://www.cocoanetics.com/2021/07/speakerclock-1-3-0/ https://www.cocoanetics.com/2021/07/speakerclock-1-3-0/#comments Sun, 18 Jul 2021 18:25:32 +0000 https://www.cocoanetics.com/?p=10714 I’ve been busy since I completely rewrote SpeakerClock in SwiftUI. That was version 1.2.0.

The App Store provides a concept called Universal Purchase, which is where purchasing an app on one device also unlocks it on all other supported platforms. In the previous version I added a Mac version. This update now adds the AppleTV version. Still a minor update, because the functionality is identical, yet all three versions benefit from improvements.

As on the other platforms I wanted to keep the UI identical, while optimising for the screen dimensions at hand. The main challenge was that contrary to the direct manipulation on the other platforms (with finger or mouse pointer) you are controlling focus on Apple TV with the remote.

So my round LED buttons have an additional white circle if they are in focus. That didn’t look nice for the LED digits however. So there I used the LED digits shadow to give it a white glow. While the timer is thus focussed you can adjust it by swiping horizontally on your Apple TV remote.

I wanted to avoid having the white glow being active while the timer is running, it would soon be annoying to the user. So if you start the timer via tapping on the remote, I place focus back on the currently active preset.

The second big change for the Apple TV concerned the user guide which is shown via the button with the question mark. Since there is no direct manipulation of a scroll view I experimented with having the individual paragraphs be focusable. But that didn’t work out well.

In the end I discarded the ScrollView and instead put the individual user guide sections on separate tabs. Since there was a lot of empty space around the text, I added some eye candy. Some of which is actually interactive.

Finally, the hidden bonus feature which possibly only developers would care about concerns the multiple layers of the app icon. The icons are all generated with original live user interface elements from the app. The non-TV icons were quite simple to do like that. On TV however an app icon has multiple layers which are shown three-dimensionally floating in an awesome parallax effect.

To achieve this for SpeakerClock I divided the multiple layers such that you have the inactive LED bars at the back, the active LED elements in the middle and the traffic light in the front.

Next up I’ll be looking into a Watch app. Although there it might make sense to wait for the iOS 15 feature where you can continue to update the screen every second. Also you should be able to use the watch as remote control for SpeakerClock running on any other platforms.

If you have any ideas or requests or questions, please feel free to contact me by email.

]]>
https://www.cocoanetics.com/2021/07/speakerclock-1-3-0/feed/ 1 10714
Rewriting SpeakerClock in SwiftUI https://www.cocoanetics.com/2021/07/rewriting-speakerclock-in-swiftui/ https://www.cocoanetics.com/2021/07/rewriting-speakerclock-in-swiftui/#comments Sun, 11 Jul 2021 12:37:29 +0000 https://www.cocoanetics.com/?p=10704 When I started out developing iOS apps, 11 years ago I put several apps on the App Store. Since they became fewer and fewer as the income from them didn’t warrant updating them. Amongst those my most successful one was iWoman, which I sold in 2015. My second-most-valuable (in terms of revenue) remained my beloved SpeakerClock, the last app standing.

I had left SpeakerClock online for the main reason that it kept producing like an average of $100 per month, even without me doing anything on it. For that reason, I didn’t want to make it free, but rather put it to a relatively high price tag of $5. There is also an In-App-Purchase of another $5. I figured “why kill the cow while it still produces some tasty milk”.

The other side effect of these price tags was that – I believe – only people who really wanted what the app was offering would actually purchase it. My philosophy with this speaking timer was to have the biggest LED digits possible, with the functionality that supports the speaking style of TED Talks, which historically have defaulted to a maximum length of 18 minutes.

Some crashes introduced by new iOS versions caused me to do small bug fixing releases (for iOS 3 in 2010, iOS 5 in 2011, and 2017 for iOS 10). Also, looking back at the release notes of those versions, I had made this exact promise:

“We have totally modernised the code base so that we can bring you some exciting new features in the next major release”

But I didn’t lie with this statement, a “next major” release would have been version 2.0. But I didn’t ever dare to turn the version number up that high. I only increased the third digit of the version number.

Apple did force me to do a new build eventually, when they cracked down on apps which weren’t updated in too long a time. And the most recent update they did themselves, when the Apple certificate had expired and they re-signed my app on their servers without me doing anything.

Enter SwiftUI

Over the last few months, I have grown very fond of SwiftUI. Being a developer on Apple platforms for more than a decade made me quite tired of having to keep writing the same MVC code countless times. And that would only get you like standard functionality, nothing truly exciting. So I jumped at the chance when one of my clients asked me to implement a new iOS Widget in SwiftUI, in the fall of 2020. Apple had turned to SwiftUI as the only way you could create such widgets because of SwiftUIs ability to produce and preserve a static view hierarchy which the system could show to the user at certain points in a timeline without substantial power usage.

My client was happy about the result and so I was tasked with the next level of SwiftUI development. I needed to implement a watchOS app, also entirely in SwiftUI. Development was quite similar to the widget, but this time I also needed to deal with user interaction and communication with the iOS counterpart app. That all took some a few months more than the widget, but again increased my SwiftUI skills tremendously.

After having delivered the watch app, I had a little extra time available to do something for myself. I do have some other ideas for apps, but my thoughts turned to SpeakerClock. I figured that this highly custom UI would lend itself nicely to be implemented in SwiftUI.

Paths in Shapes

The most important asset in the legacy code was the drawing of the big red LED digits and how they arrange themselves in portrait versus landscape, in a nice animation. So my first SwiftUI view was one that had a Path element with the SwiftUI commands adding the path elements to make up the individual bars of the LED. My first error here concerned using a GeometryReader to determine the dimensions of the path. The LED digits have a fixed aspect ratio and the drawing coordinates are based on those.

struct LEDDigit: View
{
   var digit: Int? = nil
	
   var body: some View
   {
      GeometryReader { proxy in
         let (w, h) = proxy.unitSize

         // top horizontal line
         Path { path in
            path.move(to: CGPoint(x: 24 * w, y: 7 * h))
            path.addLine(to: CGPoint(x: 60 * w, y: 7 * h))
            path.addLine(to: CGPoint(x: 62 * w, y: 10 * h))
            path.addLine(to: CGPoint(x: 57 * w, y: 15 * h))
            path.addLine(to: CGPoint(x: 24 * w, y: 15 * h))
            path.addLine(to: CGPoint(x: 21 * w, y: 10 * h))
            path.closeSubpath()
         }
         .activeLEDEffect(when: [0, 2, 3, 5, 7, 8, 9].contains(digit))
         ...
}

While this produces the correct output, it causes the individual Paths to animate separately when rotating the device. I solved this problem by moving the individual path’s code into a Shape where I am adding the bars only based on whether I am looking for the active or inactive LED elements. The path(in rect: CGRect) function hands us the required size, so we don’t a GeometryReader any more.

struct LEDDigitShape: Shape
{
   var digit: Int? = nil
   var isActive: Bool
	
   func path(in rect: CGRect) -> Path
   {
      let w = rect.size.width / 73
      let h = rect.size.height / 110
		
      var path = Path()
		
      // top horizontal line
		
      if [0, 2, 3, 5, 7, 8, 9].contains(digit) == isActive
      {
         path.move(to: CGPoint(x: 24 * w, y: 7 * h))
         path.addLine(to: CGPoint(x: 60 * w, y: 7 * h))
         path.addLine(to: CGPoint(x: 62 * w, y: 10 * h))
         path.addLine(to: CGPoint(x: 57 * w, y: 15 * h))
         path.addLine(to: CGPoint(x: 24 * w, y: 15 * h))
         path.addLine(to: CGPoint(x: 21 * w, y: 10 * h))
         path.closeSubpath()
      }
      ...
}

This is used such:

struct LEDDigit: View
{
   var digit: Int? = nil
	
   var body: some View
   {
   ZStack
   {
      LEDDigitShape(digit: digit, dot: dot, isActive: false)
         .activeLEDEffect(isActive: false)
      LEDDigitShape(digit: digit, dot: dot, isActive: true)
         .activeLEDEffect(isActive: true)
   }
}

The two members of the ZStack draw all the inactive LED elements behind the active LED elements. It still needed to be two Shapes because one shape can only have a single drawing style. The inactive elements are simply filled in a gray. The active elements are filled with red and have a red glow around them simulating some radiance.

With this approach a digit is always drawn in its entirety which lends itself to smooth resizing.

Layout and Orientation Woes

The next step was to aggregate multiple LED digits and lay them out over the screen with different positions for landscape and portrait orientations, with a smooth animation when you rotate the device.

I have basically two layouts:

  1. Hour digits, Colon, Minute digits (in a HStack)- in horizontal layout with the outer sides touching the safe area insets
  2. A VStack of Hour digits and Minute digits – in vertical layout

Sounds easy, but my attempts with HStacks and VStacks failed miserably. At the beginning of the rotation animation the digits would always get a very small frame expanding into the final one.

I can only imagine that somehow the SwiftUI layout system doesn’t remember that those are the same views. So I tried giving them static identifiers and I also tried geometry matching. But I couldn’t shake these animation artefacts. There must be some piece missing in my understanding about view identity.

In the end I came back to doing my own layout inside a GeometryReader, setting frame’s width/height and appropriate offsets (i.e. translation) for individual elements. This works very nicely and also lets me have a separate animation for the opacity of the colon.

The colon sticks to the right side of the hour digits and disappears in portrait layout. By sorting view modifiers in a certain way I was able to get this effect that the colon fades in with a slight delay.

var body: some View
{
   GeometryReader { proxy in
			
   let digitSize = self.digitSize(proxy: proxy)
   let colonSize = self.colonSize(proxy: proxy)
   let centeringOffset = self.centeringOffset(proxy: proxy)
   let isLandscape = proxy.isLandscape
			
   let timerSize = self.timerSize(proxy: proxy)
			
   Group
   {
      LEDNumber(value: model.countdown.minutes)
      .frame(width: digitSize.width * 2, height: digitSize.height)
      .animation(nil)
				
      LEDColon()
      .frame(width: colonSize.width, height: colonSize.height)
      .offset(x: digitSize.width * 2, y: 0)
      .animation(nil)
      .opacity(isLandscape ? 1 : 0)
      .animation(isPadOrPhone ? (isLandscape ? .easeInOut.delay(0.2) 
                              : .easeInOut) : nil)
				
      LEDNumber(value: model.countdown.seconds)
      .frame(width: digitSize.width * 2, height: digitSize.height)
      .offset(x: isLandscape ? digitSize.width * 2 + colonSize.width : 0,
              y: isLandscape ? 0 : digitSize.height)
      .animation(nil)
   }
   .offset(x: centeringOffset.width,
           y: centeringOffset.height)

You can see that I am specifically disabling animation with .animation(nil) for the most parts because I found that the animation otherwise is always out of sync with the rotation resizing animation. The LED colon on the other hand has its own animation with an additional delay of 0.2 seconds.

The second reason why I explicitly disabled animations is because on the Mac version those animations would lag behind the resizing of the app’s window. This resizing also switches between both layouts depending on how you drag the window corner, sort of like “responsive design” as we have seen on HTML web pages. More on Mac things further down below.

Multi-Modal Buttons

Another challenge that had me try multiple approaches concerned the preset buttons (top left) and traffic light buttons (center bottom). These buttons have a different function for a single tap (select) versus a long press (set).

The main problem is that you cannot have a simple .onLongPressGesture because this prevents the normal taps from being handled. One approach is to have a .simultaneousGesture for the long press, but then the tap action is executed right (i.e. “simultaneous”) after the long press action if you lift the finger over the button. The other approach is to use a .highPriorityGesture which again disables the built-in tap.

I ended up with the following approach which uses the gesture mask to selectively disable the long press gesture if there is no long press action and to disable the tap gesture if a long press was detected.

struct LEDButton<Content: View>: View
{
   var action: ()->()
   var longPressAction: (()->())?
   @ViewBuilder var content: ()->Content
	
   @State fileprivate var didLongPress = false
	
   var body: some View
   {
      Button(action: {}, label: content)  // must have empty action
      .contentShape(Circle())
      .buttonStyle(PlainButtonStyle())   // needed for Mac
      .simultaneousGesture(LongPressGesture().onEnded({ _ in
         didLongPress = true
         longPressAction!()
         didLongPress = false
      }), including: longPressAction != nil ? .all : .subviews)
      .highPriorityGesture(TapGesture().onEnded({ _ in
         action()
      }), including: didLongPress ? .subviews : .all)
   }
}

This approach uses a custom TapGesture in tandem with the LongPressGesture. A @State variable keeps track of the long press. We do need to reset didLongPress to false or else all subsequent taps would continue to be ignored. I found that I don’t need a dispatch async for putting it back to false.

I believe that the reason for that is that the first setting of the variable causes the body to be updated and thus the including: to disable the tap gesture while in progress. Thus the tap doesn’t fire upon releasing the long press. Good to know: The .all enables the gesture and the .subviews disables a gesture.

Contrary to other approaches I have seen on the internet this approach preserves the standard behavior of Button for highlighting, While you press a custom button like this, it makes it slightly transparent.

A Mac Version – For Free?

The huge promise of SwiftUI is that you would get a Mac version of your app for little extra work, effectively “for free”. So I decided to put this to the test also produce a macOS version. I set the targeted devices to iPhone, iPad, Mac and chose the “Optimize Interface for Mac” because that sounded to me like the better result.

This optimized mode caused some issues for my custom buttons, because they got replaced with empty round rects destroying my custom look. You can prevent this modification by adding .buttonStyle(PlainButtonStyle()).

Apart from this my code really did run as a native Mac app quite nicely. Behind the scenes though it is all Mac Catalyst. As I understand it, that means UIKit is still at the helm, on Mac just a macOS version of it.

I left the code signing settings alone as I wanted to have users be able to install the Mac and iOS versions with the same purchase. This “universal purchase” is enabled by having the same bundle identifier for both versions.

Some very minor tweaks were required for adjusting some minimum and maximum button sizes. There is a bug on macOS that stumped me for a while. Only on Mac I found that when I tapped in certain spots in my app this would cause gestures to stop working. Then when I triggered a new layout by resizing the window, everything returned back to normal.

My workaround for this was to attach the Pan Gesture (for setting the timer) only to the LED digits. This way there is no interference and all buttons continue to work normally. The system might get confused by having too many conflicting gestures on top of each other.

A side-effect of the Mac version is that you start to attach keyboard shortcuts to buttons. This was also a reason why I wanted to get Button to work with tap and long press as opposed to making a custom view that is not a button.

let title = "\(index+1)"

PresetButton()
.keyboardShortcut(KeyEquivalent(title.first!), modifiers: [.command])

This way you can trigger the preset buttons also with COMMAND plus number. And not just for the Mac app, but that works for iPads with attached keyboard as well.

That got me thinking, that maybe it would be great to allow the space bar to stop/start the timer, like we are used to from video players. For that purpose I have an empty completely black button behind the LED digits:

Button(action: { model.isTimerActive.toggle() },
       label: {
          Rectangle()
          .foregroundColor(.black)
          .frame(width: timerSize.width, height: timerSize.height)
          .onTapGesture(count: 2) { model.restoreGreenTime() }
       })
.keyboardShortcut(.space, modifiers: [])
.buttonStyle(PlainButtonStyle())

This button allows me to add a keyboard shortcut for space to act the same as a tap. Curiously having a two-tap gesture attached to the Rectangle() poses no problem.

I submitted the Mac build right after the one for iOS but initially got a shocking rejection:

The user interface of your app is not consistent with the macOS Human Interface Guidelines. Specifically:

We found that the app contains iOS touch control instructions such as tap and swipe.

The reason for that was that I put back the help screen with a text I had previously written with iOS in mind. I needed to replace mentions of swiping with dragging and instead of tapping you are clicking. I have hard coded the text and formatting for now and with and #if I can switch the text between a version for Mac and one for iOS.

Group
{
   Text("Setting the Timer")
   .font(.headline)
   .padding(.bottom, 5)
						
#if targetEnvironment(macCatalyst)
   Text("To adjust the timer, click on the LED digits and drag horizontally.")
   .font(.body)
   .padding(.bottom, 5)
#else
   Text("To adjust the timer swipe left and right.")
   .font(.body)
   .padding(.bottom, 5)
#endif					
}

Once I had made those changes the Mac app was approved very quickly.

Conclusion

I’ve experienced first hand how I can rewrite an app in SwiftUI and the great pleasure that can be had from deleting all your crufty Objective-C code when doing so.

SwiftUI is my new love and this way my app is no longer a “child from another mother”. This restores some enthusiasm in me to actually finally really add some long-promised “exciting new features”. For starters I am thinking of having a watchOS companion app which shows the timer and allows you to remote control it. Another idea might be to store my presets on iCloud so that they are the same on all my devices.

I would love to hear from you what you think about the process of re-implementing parts of apps or even whole apps in SwiftUI.

]]>
https://www.cocoanetics.com/2021/07/rewriting-speakerclock-in-swiftui/feed/ 25 10704
Kvitto 1.0.5 https://www.cocoanetics.com/2020/10/kvitto-1-0-5/ https://www.cocoanetics.com/2020/10/kvitto-1-0-5/#comments Mon, 19 Oct 2020 08:56:36 +0000 https://www.cocoanetics.com/?p=10682 Following the previous release, I received word that Apple had added a new kind of sales receipt which is used for IAP testing. This release adds support for those.

Changes

  • NEW: Added support for indefinite length encoding for containers
  • NEW: Added support for decoding date strings with a UTC offset instead of Z.

For first change was done in DTFoundation which contains DTASN1, our ASN.1 decoder. When writing an ASN.1 file you would normally know all lengths of all containers and primitive values and so you could specify the length following each tag. Apple was doing so in all sales receipts so far and we didn’t have any issue with it.

In Xcode 12, Apple added a new way of testing In-App purchases (see Introducing StoreKit Testing in Xcode WWDC 2020 talk for more info).  This broke parsing.

They started using indefinite length encoding for various container elements, including but not limited to the PKCS7 container. Once I had implemented that, I found that they also had a certain date with +0300 as timezone, instead of Z (for UTC) as we had previously encountered. So I needed to add this variant as well.

The update was tagged on GitHub (which also serves as new release for Swift Package Manager) and is also available via Cocoapods.

]]>
https://www.cocoanetics.com/2020/10/kvitto-1-0-5/feed/ 11 10682
Kvitto 1.0.4 https://www.cocoanetics.com/2020/09/kvitto-1-0-4/ https://www.cocoanetics.com/2020/09/kvitto-1-0-4/#comments Mon, 28 Sep 2020 08:05:05 +0000 https://www.cocoanetics.com/?p=10637 Kvitto is an open source component that lets you parse Apple sales receipt files, for example to determine if the user has an active auto-renewing subscription on device.

This new release fixes an urgent issue that appeared the first time on September 24th, in iOS 14, where about half the sales receipts could not be parsed. I also added support for Swift Package Manager 2 weeks ago and had forgotten to announce the release, so there you go.

Changes

  • NEW: Support Swift Package Manager
  • FIXED: dates with fractional seconds would not be parsed
  • FIXED: Relax check for sequence in root of PKCS7 container, as Apple might sometimes supply only 3 elements instead of 5

After seeing and collaborating with a few motivated developers on adding SPM support to DTFoundation and DTCoreText I felt empowered to tackle this for Kvitto myself.

About the fixed bug… I got first word about new issues with Kvitto from Canada on September 25th:

We’ve been getting some emails in the past couple days from users saying their subscription isn’t validating.

At first I shrugged this off, because I hadn’t changed anything, and why would Apple change something here? But then in the evening of September 27th, I got a detailed issue report from Germany, pinpointing the issue as being related to Apple now sometimes including fractional seconds on date fields. So instead of 2020-09-27T12:07:19Z we would now – sometimes, not always – get 2020-09-27T12:07:19.686Z – which NSDateFormatter is not smart enough to ignore.

My fix is basically to check the length of the string and then use the correct date format for that.

The release has been tagged on GitHub, thus is available via Swift Package Manager on the master branch, and it has also been released via CocoaPods trunk.

]]>
https://www.cocoanetics.com/2020/09/kvitto-1-0-4/feed/ 19 10637
DTCoreText 1.6.25 https://www.cocoanetics.com/2020/08/dtcoretext-1-6-25/ https://www.cocoanetics.com/2020/08/dtcoretext-1-6-25/#comments Mon, 24 Aug 2020 16:30:34 +0000 https://www.cocoanetics.com/?p=10635 This goes hand-in-hand with the latest update to DTFoundation, which added support for Swift Package Manager.

Changes

  • FIXED: URLs containing CJK characters are not parsed
  • FIXED: iOS 13 openURL crash
  • FIXED: References to deprecated classes
  • FIXED: Cannot parse CSS with empty font-family
  • FIXED: iOS 14 warnings
  • NEW: Swift Package Manager Support

This update has been tagged on GitHub and is available via CocoaPods.

]]>
https://www.cocoanetics.com/2020/08/dtcoretext-1-6-25/feed/ 2 10635
DTCoreText 1.6.23 https://www.cocoanetics.com/2019/09/dtcoretext-1-6-23/ https://www.cocoanetics.com/2019/09/dtcoretext-1-6-23/#comments Wed, 11 Sep 2019 07:07:41 +0000 https://www.cocoanetics.com/?p=10613 Hot on the heels of the previous maintenance release, which was only 3 Months ago, this one was required because Apple no longer allows Apps to contain references to UIWebView.

The main change was that from both DTCoreText as well as, previously, DTFoundation we removed some helper methods for UIWebView. A view used by the demo to embed YouTube videos was moved to the Demo app. There was a bug in DTImageTextAttachment which caused some images to be square.

There was a missing macro which prevented compiling with a really old deployment target. That was also addressed.

There is a known issue in iOS 13 when you try to use dynamic type fonts when parsing HTML. If you try to set a dynamic type font’s name or family via parsing option, you get this warning:

CoreText performance note: Client called CTFontCreateWithName() using name ".SFUI-Regular" and got font with PostScript name "TimesNewRomanPSMT". For best performance, only use PostScript names when calling this API.

Well, the problem is that we have no API that would allow you to set the font instance itself on DTCoreText, but only to set a font or family name. This is transferred to an internal font descriptor which is handed down to sub-tags, being modified as needed. The main problem is that if you get the font descriptor for a dynamic font, there are other attributes set on the descriptor which are necessary for proper dynamic point size and font face resolution.

What’s needed here is a way to set a font itself – not just its name – as an option and to create a suitable font descriptor for it that preserves the dynamic type capability. If somebody were to sponsor the creation of this functionality, I’d be happy to add it. You can email me at oliver@cocoanetics.com to inquire. Of course, pull requests are welcome as well.

The update is tagged on GitHub and available via CocoaPods.

]]>
https://www.cocoanetics.com/2019/09/dtcoretext-1-6-23/feed/ 1 10613
DTFoundation 1.7.14 https://www.cocoanetics.com/2019/09/dtfoundation-1-7-14/ https://www.cocoanetics.com/2019/09/dtfoundation-1-7-14/#respond Tue, 10 Sep 2019 12:14:31 +0000 https://www.cocoanetics.com/?p=10611 Several recent pull requests, as well as an acute deprecation of UIWebView made it necessary to push out this maintenance update. Even more so, because this framework is a dependency of DTCoreText which also needs an update.

Changes

  • FIXED: incorrect public header location for tvOS framework
  • FIXED: Nullability tags for DTReachability, NSString+DTPaths
  • FIXED: Added tvOS support for DTASN1
  • FIXED: Several implicit self retain warnings
  • REMOVED: UIWebView helpers as this causes submission problems

The update is tagged on GitHub as well as available via Cocoapods.

]]>
https://www.cocoanetics.com/2019/09/dtfoundation-1-7-14/feed/ 0 10611
DTCoreText 1.6.22 https://www.cocoanetics.com/2019/06/dtcoretext-1-6-22/ https://www.cocoanetics.com/2019/06/dtcoretext-1-6-22/#comments Fri, 28 Jun 2019 13:24:50 +0000 https://www.cocoanetics.com/?p=10604 The previous release was almost 2 years ago, and several people requested this one, so I finally got around to doing it.

This is mostly a maintenance release, my reasons for not maintaining this project I did outline in the post accompanying the previous release. Here are the commits contributing to this release.

The biggest change came from Duncan Cunningham who removed support for static library and instead moved the default.css into the framework resources. DTCoreText uses this style sheet for formatting default values. In order to support static libraries is had to be compiled into embedded binary code. This proved problematic in newer Xcode versions.

Other fixes addressed some edge cases like illegal spaces in URLs, fixing a crash when using images in LI elements, fixing a ton of Xcode warnings and some small build fixes for tvOS.

I am thanking those who stubbornly stick with DTCoreText and keep contributing to it. The new release is available on Cocoapods and has been tagged on GitHub.

]]>
https://www.cocoanetics.com/2019/06/dtcoretext-1-6-22/feed/ 18 10604
BarCodeKit 1.3.2 https://www.cocoanetics.com/2018/06/barcodekit-1-3-2/ https://www.cocoanetics.com/2018/06/barcodekit-1-3-2/#comments Thu, 07 Jun 2018 14:25:41 +0000 https://www.cocoanetics.com/?p=10567 This new release of BarCodeKit fixes an issue with iOS-compatibility and several new warnings pointed out by Xcode 9.4.

Changes

  • FIXED: Podspec didn’t specify that pod is iOS compatible
  • FIXED: Some 9.4 warnings
  • FIXED: The option to suppress quiet zones caused barcodes to be shifted out of the center of the generated images.
  • CHANGED: Option to suppress quiet zones renamed to BCKCodeDrawingSuppressQuietZones

Apparently I broke iOS support when I added Mac support because I specified the platform OSX in the pod spec, not realising that this would remove compatibility with iOS. The correct way, pointed out via pull request by Christopher Streel was to instead specify platform-specific version numbers.

I also used the opportunity do eliminate a few new warnings by Xcode 9.4. And when looking at the demos I noticed that a bug had been introduced supporting suppression of quiet zones. So I fixed that as well.

The update is tagged on GitHub and available via Cocoapods.

]]>
https://www.cocoanetics.com/2018/06/barcodekit-1-3-2/feed/ 4 10567
DTBonjour 1.1.3 https://www.cocoanetics.com/2017/11/dtbonjour-1-1-3/ https://www.cocoanetics.com/2017/11/dtbonjour-1-1-3/#comments Wed, 22 Nov 2017 11:01:31 +0000 https://www.cocoanetics.com/?p=10551 DTBonjour also needed to be refreshed for an old codebase I was reviving. The previous release 1.1.2 was also from almost 2 years ago. The update is tagged on GitHub was well as available via Cocoapods.

  • FIXED: Bonjour browser finds duplicate TXT records, name is now set as UUID
  • CHANGED: Sending now occurs on background queue
  • ADDED: tvOS support to the pod spec

I didn’t quite like the workaround with using the UUID, because if you leave it blank the system uses the device name. But I have to admit that I wasn’t actively developing on something using DTBonjour and so I believed the contributors assessment that this approach would be beneficial.

Thanks to MatthewCawley and scottcarter for their contributions to this release!

]]>
https://www.cocoanetics.com/2017/11/dtbonjour-1-1-3/feed/ 3 10551
DTDownload 1.1.3 https://www.cocoanetics.com/2017/11/dtdownload-1-1-3/ https://www.cocoanetics.com/2017/11/dtdownload-1-1-3/#comments Wed, 22 Nov 2017 10:47:53 +0000 https://www.cocoanetics.com/?p=10548 In most modern projects we had moved to NSURLSession, but for a very dated codebase using DTDownload via Cococapods I needed to craft a new release which contained all the fixes for the 2 years since the version 1.1.2 release. It is tagged on GitHub and available via Cocoapods.

Changes

  • ADDED: downloads can be paused by setting concurrent downloads to 0
  • ADDED: Support for If-Modified-Since, treated as cancel in case of 304
  • ADDED: Download cancelling
  • ADDED: Progress for DTDownloadCache + DTDownload
  • FIXED: Problem with translucent images getting a black background
  • FIXED: Various build issues and failing unit tests
  • FIXED: Crash trying to decompress too large images
  • FIXED: Problem parsing CEST Time Zone
  • FIXED: Unnecessary image decompression if there is an image already in memory
  • FIXED: Podfile, DTAsyncFileDeleter is now subspec
  • CHANGED: Limit image decompression to single background serial queue

Some updates to the pod spec were necessary due to changes in DTFoundation, where DTAsyncFileDeleter was moved into a subspec.

Thanks to gugmaster for his contributions to this release!

]]>
https://www.cocoanetics.com/2017/11/dtdownload-1-1-3/feed/ 2 10548
DTFoundation 1.7.13 https://www.cocoanetics.com/2017/11/dtfoundation-1-7-13/ https://www.cocoanetics.com/2017/11/dtfoundation-1-7-13/#comments Wed, 22 Nov 2017 10:27:19 +0000 https://www.cocoanetics.com/?p=10545 Sorry, I was to lazy to publish release notes for version 1.7.12 (published in February 2017) and version 1.7.13 (published two weeks ago). So here they are. The release is tagged on GitHub as well as released via Cocoapods.

Changes

  • FIXED: DTReachability stops working when app is backgrounded
  • FIXED: DTASN1Parser fails to parse date if user manually switched to 12-hour clock
  • FIXED: An incorrect autorelease
  • FIXED: Some problems making it non-extension friendly.
  • FIXED: Linting problems in pod spec
  • FIXED in 1.7.12: [DTAnimatedGIF] An invalid file path causes a crash on loading
  • CHANGED: Draw DTTiledLayerWithoutFade on main thread
  • CHANGED: Minimum macOS requirement is now OSX 10.8

I found that apparently some changes to Cocoapods and/or Xcode 9 caused the pod spec linting to fail on the macOS platform. The main reason was that certain syntactic sugar accessing dictionary and arrays via square brackets apparently weren’t added for macOS before 10.8. Hence the increased minimum requirement, because I couldn’t be bothered to adapt the code to build for 10.6 and 10.7.

Thanks to ChiHocbrentdaxsirnacnudJohennes and gugmaster for their contributions to this release!

]]>
https://www.cocoanetics.com/2017/11/dtfoundation-1-7-13/feed/ 1 10545
DTCoreText 1.6.21 https://www.cocoanetics.com/2017/08/dtcoretext-1-6-21/ https://www.cocoanetics.com/2017/08/dtcoretext-1-6-21/#comments Mon, 28 Aug 2017 14:39:38 +0000 https://www.cocoanetics.com/?p=10506 The previous release of DTCoreText was in February, 7 months ago, so it was about time to release an update for the about 50 commits that were made by community contributors since then.

Changes

  • ADDED: Support for <p> text-indent
  • ADDED: Support width attribute in percent for text attachments
  • ADDED: Ability to abort HTML parsing
  • ADDED: Archiving
  • ADDED: Improved support for macOS
  • FIXED: Incorrect file name in import should be DTCSSStylesheet.h
  • FIXED: HTML generation with multi-line links
  • FIXED: Do not add Apple-converted-space to attributed string when processing custom HTML attributes
  • FIXED: Ignore list style shorthands that are not strings
  • FIXED: Wrong attributed substring passed to generic custom view when using truncation
  • FIXED: Avoid unnecessary drawing of DTAttributedTextContentView if it is being deallocated
  • FIXED: Changing properties on DTCoreTextLayoutFrame would not update layout, resulting in incorrect sizing information being returned

I also went through the GitHub Issues and closed all the ones that have neither had any discussion in a long while. Most of them were questions which were long answered or are requests which nobody is willing to implement.

There are two requests/questions which I would like to answer here:

  1. How about supporting synthetic italics for Chinese?
  2. How about supporting HTML tables?

iOS TextKit does not have any fonts for showing Chinese glyphs in italic. The effect can be achieved by adding a slanting transform to the font. In fact this is already done during font matching. Why this is not working for Chinese script is a mystery to me. I’m interested if somebody can provide a fix via pull request.

About HTML tables… the problem here is that in order to support that we’d have to do two major things: First we need to parse the HTML tags related to tables (table, td, tr, etc.) into some temporary storage and then we need to build a text attachment from that. The second step would be to generate an image or a custom subview that is able to properly layout and render the table contents.

In all the years I have never met anybody using DTCoreText who would be willing to pay me for implementing that.

The maintenance update is tagged on GitHub and also available via Cocoapods.

How about DTCoreText 2.0?

And actually… there are other changes to DTCoreText which would need to happen before adding any such new features could reasonably be added. I’d rather start working on a successor to DTCoreText 1.x.

To name a few examples of different design choices for a new start:

  • Nowadays, I would write it in Swift
  • We could still use libxml for parsing HTML but I would make it UIKit-compatible exclusively.
  • UILabel and UITextView support attributed strings for many years now, there is simply no good reason to keep using the attributed label from DTCoreText 1.x.
  • Text attachment would solely be modelled with NSTextAttachment, including support for asynchronous images.
  • Via UITextView we would also get automatic support for Rich Text editing. Instead of using DTRichTextEditor, editing would then be built into iOS and macOS.
  • Unfortunately Apple still doesn’t give us support for creating text lists, although iOS seems to internally support NSTextList and NSTextBlock. But we can probably hack around that in a way that is App Store-compatible.
  • Using TextKit’s layout facilities it would also finally be possible to have text flow around floating images

I’ve gathered a lot of experience dealing with attributed strings without using DTCoreText, solely relying on built-in functionality. And from that I can tell you that it is possible and worth the effort.

I’d love to have a company sponsor a new start. If you’re interested to help fund it, please get in touch.

]]>
https://www.cocoanetics.com/2017/08/dtcoretext-1-6-21/feed/ 2 10506
DTCoreText 1.6.20 https://www.cocoanetics.com/2017/02/dtcoretext-1-6-20/ https://www.cocoanetics.com/2017/02/dtcoretext-1-6-20/#comments Thu, 23 Feb 2017 18:51:10 +0000 https://www.cocoanetics.com/?p=10433 Since the previous maintenance release was 4 months ago, I decided to make a new release with the contributions made since then. DTCoreText is not being actively developed further by me because by this time the HTML-parsing capabilities built into iOS 9 and 10 are sufficient for most peoples needs. But since I keep getting good pull requests (with unit tests a welcome extra) I keep releasing these every couple of months.

Changes

  • FIXED: Crash when img tag had an invalid file URL to an animated GIF
  • FIXED: Superfluos import for framework-based umbrella header causing build issues
  • FIXED: Link will be nil when cleanString contains Chinese characters
  • FIXED: DTCoreTextLayoutFrame memory leak
  • FIXED: Certain truncations might cause a crash
  • FIXED: Skip unpaired bracket } & fix NSUInteger underflow in CSS parser
  • FIXED: Space to “apple converted space span” conversion for 2 spaces
  • FIXED: Text range for text block (for drawing background rectangle) might be computed incorrectly
  • FIXED: Crash with IFRAME src attribute had less than 2 characters
  • ADDED: Look for `DTCoreTextFontOverrides.plist` in the main bundle if its not found in the local bundle
  • ADDED: Expanded character decoding support in stringByReplacingHTMLEntities

Thank you to Kasper Weibel, David X. Lau and several contributors who have not set a proper name on Github, making it impossible for me to thank them by name.

At the time of this writing I have 80 open issues, but for the reason stated above I am going to close all of them for lack of activity. I’ve seen this practise in action on the Fastlane project. I will personally only ever work on an issue if somebody hires me to do so. Other issues will only be addressed if somebody provides a pull request.

For that reason, issues that basically say “I am a beginner and I cannot get the project to compile” are rather useless. Sorry, but I don’t have time to give free support or mentoring.

I’d rather be working on starting a new project that adds some missing functionality that Apple still hasn’t added to HTML parsing in iOS, specifically support for editing bulleted and numbered lists. In particular I am interested in adding proper handling for lists to UITextView which I am using in this Babyforum.at app which we recently built for a client. I did it once before in DTRichTextEditor, we should be able to do that in a UITextView subclass as well.

But enough ranting, for those people who still insist on using DTCoreText, the update is tagged on GitHub and available via Cocoapods. I don’t know about Carthage, sorry. But I think somebody mentioned that this works as well.

]]>
https://www.cocoanetics.com/2017/02/dtcoretext-1-6-20/feed/ 1 10433
POEditorAPI 1.1.0 https://www.cocoanetics.com/2016/12/poeditorapi-1-1-0/ https://www.cocoanetics.com/2016/12/poeditorapi-1-1-0/#comments Sat, 10 Dec 2016 18:40:55 +0000 https://www.cocoanetics.com/?p=10331 In the month since the initial release of  the POEditor.com API and command line utility, I did some refinements which become necessary to support ongoing development projects. Generally speaking you should use the poet tool without parameters to refresh your strings files.

The utility requires that all translations have their context set, or else we don’t know which file they go into. Now, if you go the outmoded route of importing a strings file into POEditor.com then the context isn’t correctly set. You can fix this by manually updating it to “Localizable.strings”. The modern way that has long replaced this is to export a XLIFF file in the development language directly from Xcode. This makes sure that all contexts are set, usually either to a strings file or sometimes a storyboard file.

Changes

  • FIXED: POEditor class/methods was not visible to importing app
  • FIXED: Don’t write empty files or empty plurals
  • CHANGED: Removed workaround that would use en-US as en language.
  • ADDED: Information in README on how to uses it with Cocoapods (now that it works)
  • ADDED: The poet command line utility now has a parameter to do a one-off export in a given format

The command line utility uses the JSON export format because this also contains plurals. For the BabyCheck app I needed to be able to export the XLIFF file for each language because I am building the localised checklists that come bundled with the app from that. This is why I added a parameter to poet which lets you specify the output file format.

The second change was that you couldn’t use it via CocoaPods because the framework classes did not have the correct public accessibility tags. This release fixes this. I also added some info to the README file to show how to do that and how to do a smoke test.

The ability to automate the process of updating localisations has proven to save me a great amount of time so far. I hope that it can do so for you as well. I would love to hear from you what challenges you have in your localisations and maybe see how poet and/or this API wrapper could be enhanced to address these.

This update is tagged on GitHub as well as pushed to CocoaPods trunk.

]]>
https://www.cocoanetics.com/2016/12/poeditorapi-1-1-0/feed/ 1 10331
DTCoreText 1.6.19 https://www.cocoanetics.com/2016/10/dtcoretext-1-6-19/ https://www.cocoanetics.com/2016/10/dtcoretext-1-6-19/#comments Tue, 11 Oct 2016 17:21:08 +0000 https://www.cocoanetics.com/?p=10322 Hot on the heels of the previous release for DTCoreText this update hot-fixes several issues which were introduced in the previous release.

Changes

  • FIXED: Incompatibility with iOS 7, using containsString
  • FIXED: Memory Leak in DTLazyImageView, using NSURLSession
  • FIXED: [Demo] When loading remote images the relayout needs to be done on next runloop
  • FIXED: Incorrect tabulator used for list items
  • FIXED: In default.css P tags had a -webkit-margin-before (which gets parsed since 1.6.18) causing superfluous extra space before paragraphs

I decided to push this release without delay so that everybody needing DTCoreText can get fixes for bugs I introduced in 1.6.18 mostly fixing Xcode 8 warnings. Tips are welcome to PayPal oliver@cocoanetics.com.

The update is tagged on GitHub and available via CocoaPods.

]]>
https://www.cocoanetics.com/2016/10/dtcoretext-1-6-19/feed/ 1 10322
DTCoreText 1.6.18 https://www.cocoanetics.com/2016/09/dtcoretext-1-6-18/ https://www.cocoanetics.com/2016/09/dtcoretext-1-6-18/#comments Fri, 30 Sep 2016 14:29:41 +0000 https://www.cocoanetics.com/?p=10318 Since the prior release of DTCoreText, 9 months ago, a few issues became apparent with Xcode 8. Some people contributed cool stuff like support for use in app extensions as well as tvOS support.

Changes

  • ADDED: tvOS Support
  • ADDED: Property to better support display remote images with DTAttributedTextCell
  • ADDED: Extension sub spec (which removes some things that cannot be used in Extensions)
  • ADDED: Parse margin-top as paragraphSpacingBefore
  • ADDED: Support for list style with roman numerals
  • ADDED: 10% performance increase parsing CSS styles that only have a single part
  • ADDED: Usage of custom font name via CSS in programming guide
  • FIXED: Potential crash on iOS 10 from more strict CoreText functions
  • FIXED: Crash when list-style-image was not a string
  • FIXED: Crash when comparing lines and one of them was nil
  • FIXED: Several Xcode 8 build warnings
  • FIXED: All deprecation warnings related to CoreText text alignment constants
  • FIXED: NSURLConnection deprecation by using NSURLSessionDataTask instead
  • FIXED: Deprecation warnings related to percentEncoding, replaced with HTML entity encoding
  • FIXED: Several Carthage build problems

Thank you for your contributions, Philipp Schmid, David X. Lau, James Hurst, Brandon Tram, Alex WU, Kai Maschke,  Ryan Maxwell, Ian Ynda-Hummel, Florian Friedrich. (I can only thank people who have a proper name set up on GitHub)

One particular annoyance resulted from a bug in CocoaPods where the prepare_command of the podspec is not being executed. I am using this to generate a C file from the default CSS file and if it is not run, the linting of the spec fails. So as an immediate workaround I added a copy of this file to the repo.

The most work I needed to do on DTLazyImageView which required replacing the prior NSURLConnection logic with NSURLSessionDataTask while preserving the progressive download capability.

The update is tagged on GitHub (for Carthage) and available on Cocoapods.

]]>
https://www.cocoanetics.com/2016/09/dtcoretext-1-6-18/feed/ 1 10318
DTFoundation 1.7.11 https://www.cocoanetics.com/2016/09/dtfoundation-1-7-11/ https://www.cocoanetics.com/2016/09/dtfoundation-1-7-11/#respond Mon, 26 Sep 2016 14:07:42 +0000 https://www.cocoanetics.com/?p=10311 With the prior update to DTFoundation having been 5 months ago, there were two main areas where some tweaks were necessary: support for tvOS and fixing a build error on Xcode 8.

Changes

  • FIXED: Xcode 8 build error in Runtime
  • ADDED: tvOS Subspec

Thanks to Ryan Maxwell for the adjustments necessary to for tvOS support. Thanks to Fawaz Tahir for the Xcode 8 compatibility fix.

The update is tagged on GitHub (for Carthage) and also available on Cocoapods.

]]>
https://www.cocoanetics.com/2016/09/dtfoundation-1-7-11/feed/ 0 10311