swiftui

Back Open Paginator
08.11.2024 21:39
denid88 (@denid88@mastodon.social)

Developing the Profile Screen for Healthcare iOS App

Focused today on my healthcare-focused iOS app. 🏥📱 Developed the UI screen for the Profile section, enhancing user interaction and functionality. Excited to continue building and improving the app! 💪✨

Let me know if you’d like any further changes!




Show Original Post


08.11.2024 20:13
swiftdevjournal (@swiftdevjournal@mastodon.world)

The following article shows how to build complex layouts in SwiftUI using grids:

bugfender.com/blog/swiftui-gri

#SwiftUI




Show Original Post


08.11.2024 20:00
FreemiumKit (@FreemiumKit@mastodon.social)

Implementing features should be easy.

That's why we added the `freeIf` parameter:
✨ One line of code
🔄 Automatic state handling
🎯 Perfect for usage limits
🎨 Native feel

Here's how simple it is: 👇





Show Original Post


08.11.2024 16:48
pauljadam (@pauljadam@mastodon.social)

Techniques app updated with new Horizontal Scroll Views good/bad example that applies WCAG's Reflow success criterion to avoid horizontal scrolling at 320 or less screen width and adds right and left arrow buttons as single tap alternatives. apps.apple.com/app/accessibili





Show Original Post


08.11.2024 15:30
andykent (@andykent@hachyderm.io)

Reorderable Favourites is one of those small features I’ve wanted for ages but kept getting pushed back for bigger things. Like most things in #SwiftUI Drag & Drop is great when it works and cursed when it doesn’t. Very much hoping it works smoothly for everyone!





Show Original Post


08.11.2024 12:55
dimitribouniol (@dimitribouniol@mastodon.social)

Today was interesting: got a bunch of nothing done, but learned a lot about how modules in JS work, and will probably refactor how JS is loaded site-wide as a result haha. Tomorrow, let's do that. See you then!

🔜 Tomorrow’s stream: youtube.com/live/Hslp-RVp9qQ
⏮️ Playlist so far: youtube.com/playlist?list=PLRx
📲 Download Jiiiii: apps.apple.com/app/apple-store




Show Original Post


08.11.2024 11:30
dimitribouniol (@dimitribouniol@mastodon.social)

I'm making a seasonal anime guide app, in the open for all to experience and learn from!

Let's flesh out the merge page to make progress there.

Come chill with me: youtube.com/live/F784ffsitH8




Show Original Post


08.11.2024 02:10
2024 (@2024@rhonabwy.com)

Head’s up: this post is a technical deep dive into the code of DocC, the Swift language documentation system. Not that my content doesn’t tend to be heavily technical, but this goes even further than usual.

The Setup

While I was working on some documentation for the snippets feature in DocC, I ran into an issue with the mechanism to preview documentation. As soon as I added a snippet to an example project, the documentation would fail to preview about half the time. The command I use is:

swift package --disable-sandbox preview-documentation --target MyTarget

When I first started debugging this, I wasn’t sure what caused the issue. I opened a bug in swift-docc-plugin (spoiler: the bug wasn’t in swift-docc-plugin), thinking at first that it was always failing. As it turns out, it wasn’t always failing – my luck was just poor, and the issue intermittent. I had several commits in one of my side projects that added snippets, which I used to work through my documentation of the feature. In order to write up the issue with reasonable reproduction steps, I created a series of commands to verify the behavior I saw. The flow is pretty simple:

  1. clone the example project that illustrates the problem
  2. go to the commit that shows it working
  3. invoke the preview
  4. switch to the commit that shows it failing
  5. invoke the preview

At this point, I didn’t realize that the issue was intermittent, so I iterated back and forth between commits, cleaning the .build directory to see if that made a difference, and then ultimately noticed a change in behavior. At one point where I expected it to fail, it worked. Ah, glorious: A heisenbug. At least now I knew that I’d have to repeat process multiple times to get the issue to show. With that in mind, I was able to nail down the change in my project that started to illustrate the issue – it was when I added the first snippet.

There’s another project (the exemplar, really) that hosts snippets in its documentation – swift-markdown – that *never* exhibited this problem. That was a real head scratcher. But I did have a reliable reproduction, so I focused on that.

When I work on an intermittent bug, I try to get a debugger attached on the code that’s behaving badly. Because this was invoked through a SwiftPM plugin, I had my work cut out for me. Command plugins are separate executables that Swift package manager invokes internally. It is obnoxious to get a debugger attached to it. You can’t easily do it directly from within Xcode, because Xcode isn’t launching the executable. There’s a conversation about how to wrangle debugging a SwiftPM plugin on the Swift Forums that covers some of this. The way I resolved it this time is to put in a long sleep() command in the code of the plugin, run it through SwiftPM, use the terminal to hunt down the process ID that SwiftPM invoked, and attach the debugger to that ID. This is kind of a nasty manual process, so I used sleep(30) – I’m just not that fast at wrangling all the tools for this. I managed to get attached… and then realized I didn’t need to.

While I was looking at the process list through the terminal to get the process ID, I spotted that the process in question (the plugin) was invoking yet ANOTHER process in turn. I actually knew this previously, and just plain forgot. The swift package preview-documentation command is a light wrapper around docc’s preview command. While I wasted some time with the plugin, this made debugging significantly less painful. I could invoke an example using the docc binary directly. And yeah, it moved the target for what had the bug – it wasn’t in swift-docc-plugin.

Debugging preview in DocC

I closed the issue in the plugin and opened a new issue in swift-docc, summarizing what I’d learned and how to reproduce the issue. It was the end of a day when I got to this point, so I left things alone and came back the next morning. When I opened the issue, I verified the issue using the version of docc released with the Swift toolchain. In my case – that meant the version included in the toolchain that ships with Xcode 16.1.

When I jumped back in, I had a the intent to verify the same issue exhibits with the latest code – against the main branch. The issue request form asked for any issues to be verified against main in order to verify that it hasn’t already been resolved. There were also some comments in the issue – David referenced some other work pending that resolved some flaky tests, that – at a guess – might have an impact. So I buckled down to use the latest development branch of docc and repeat the process to verify the issue.

One of the quirks of verifying this issue is that docc is a separate project from the javascript single-page browser app (swift-docc-render) that displays the content. When you’re running docc from the main branch, it doesn’t know where that content lives – you need to tell it. Fortunately, that’s pretty easy. You set a specific environment variable and docc uses that to know where to load the content.

With that in place, and the example process invocation from my debugging the prior day, I had a way to run this directly. In the terminal, it looks something like:

export DOCC_HTML_DIR=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/docc/render//Users/heckj/src/swift-project/swift-docc/.build/debug/docc preview \/Users/heckj/src/Voxels/Sources/Voxels/Documentation.docc \--emit-lmdb-index \--fallback-display-name Voxels \--fallback-bundle-identifier Voxels \--additional-symbol-graph-dir /Users/heckj/src/Voxels/.build/plugins/Swift-DocC\ Preview/outputs/.build/symbol-graphs/unified-symbol-graphs/Voxels-7 \--output-path /Users/heckj/src/Voxels/.build/plugins/Swift-DocC\ Preview/outputs/Voxels.doccarchive

I prefer to use Xcode when debugging and fortunately that’s not too hard to arrange. In order to set it up, I opened the Package.swift file of the docc project with Xcode. It sets up the targets for you, and with a package like this, tends to default to the package target. I shifted the platform I was building for to “My Mac”, and the target to the docc executable. With those set, I opened “edit scheme” for that combination.

Xcode lets you set environment variables and pass arguments to the executable when you invoke “run”. That’s perfect for what I was doing – working to easily reproduce the issue where I could debug it.

The scheme editor in Xcode for the run mode, that shows arguments set and an environment variable to make it easier to debug docc.

I set the DOCC_HTML_DIR environment variable and set up the arguments from my example. One thing I had not caught when I first did this was that the path in one of the arguments included a space. Once I realized there was space, and run wasn’t working, I added a / character to escape the space in the name (within “Swift-DocC Preview/outputs/”). With that in place, I was able to run the code and see the results, as well as run the debugger. The issue was, indeed, repeating itself with the main branch.

Once I had that set up, checking the pull request that David mentioned in the issue was a piece of cake. I’ve had a poor time with Xcode handling changing a git branch underneath it, so I closed Xcode and updated the branch, using the gh executable as a helper. When you look at a pull request on GitHub, the CODE button provides you with a command line snippet that you can copy and paste to get it on your local machine. In this case:

gh pr checkout 1070

Once it was checked out, I re-opened Xcode, and was just a matter of running a few more times. Fortunately, scheme settings that Xcode uses when you tweak run arguments aren’t generally overwritten when you switch to another branch. I had some hopes this might solve the issue, but they were dashed pretty quickly. With 5 runs, I was able to verify that the code update didn’t make a difference in my example.

Verifying beyond swift-docc-plugin

Since I didn’t get the quick win with the pull request, it was time to dig further. Switching back to the main branch, I took it from the top. I start by looking at how code gets executed within docc.

The entrance point for docc (https://github.com/swiftlang/swift-docc/blob/main/Sources/docc/main.swift) very quickly leads to a swift argument parser setup (https://github.com/swiftlang/swift-docc/blob/main/Sources/SwiftDocCUtilities/Docc.swift). It is quick to see its broken up into subcommands, one of which is preview. Finding the code for that subcommand is less than obvious just scanning at the folders and files, but command-clicking in Xcode gets you right there: https://github.com/swiftlang/swift-docc/blob/main/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift. The preview subcommand code, in turn, uses a PreviewAction that has a perform() function where the “work gets done”. The gist of which is:

When I first ran into this issue, what I saw from invoking a preview was an output that was missing the name of the module when it displayed the preview. I was reasonably familiar with what the contents of that directory should look like, so I ran it multiple times and captured a copy of the directory structure that it built when it worked, and another when it didn’t. Comparing the two, the difference was that the top-level module for my example project just wasn’t appearing. The directories included all the files for the symbols in my module, just not the module itself.

With that knowledge in hand, when I got to this “convert first, then display” setup, I knew the path to search down into was the convert action. I also knew that it had something to do with the top-level module, since all the symbols were there – it was missing the top level module in the output.

Spelunking into convert

If I had to pick a “heart of DocC”, it would be this conversion process.

The high-level workflow takes in two different kinds of data – one or more symbol graphs and a documentation catalog – and assembles a documentation archive from them. The result isn’t plain files with HTML inside them. Instead it’s a collection of the data that represent those symbols (in JSON) that can be rendered – into HTML, or really any other target. The rendering happens with a different bit of code (that’s the swift-docc-render project that I mentioned).

Symbol graphs are generated by the compiler (or other code, really – but generally by the compiler). But symbol graphs along don’t have all the details in a way that’s easy to collect and render them. The relationships between symbols, the type of symbol, and so on, gets cleaned and re-arranged in the convert process. It also mixes in the writing and resources that you provide in the docc catalog. This lets DocC override or add in content, as well as the provide things that don’t exist in the raw code symbols, such as articles and tutorials.

The code in ConvertAction is fairly complex as there’s a bit of abstraction there that makes it a little harder to parse. It abstracts the producer of data and the consumer, and has additional bits to support tracking documentation coverage, captures diagnostics for issues mixing the files together so they can be played back to tooling, and other options, such as building an index. All this is encapsulated in the _perform method. That method, in turn, runs this bit of code:

conversionProblems = try ConvertActionConverter.convert(  bundle: bundle,  context: context,  outputConsumer: outputConsumer,  sourceRepository: sourceRepository,  emitDigest: emitDigest,  documentationCoverageOptions: documentationCoverageOptions)

While ConvertActionConverter is a jump around the project code, it’s encapsulated pretty well. It’s fairly straight forward to read and understand what’s happening. There’s an inner function with a lot of comments in the flow of that method that made it harder to for me to track what was happening, and where the function boundaries were. Once I realized it what it was, I read around it to again look for that “where’s the core work happening”.

The heart of the convert function is:

let entity = try context.entity(with: identifier)guard let renderNode = converter.renderNode(for: entity) else {    // No render node was produced for this entity, so just skip it.    return}                    try outputConsumer.consume(renderNode: renderNode)

This code is wrapped inside a function context.knownPages.concurrentPerform that iterates through the known pages. I wasn’t sure where it might be dropping the top-level module, so I started with good old fashioned printf debugging. That also exposed a bunch of new types to explore and learn about:

I started off with a breakpoint on the bit of code that sets the entity from the identifier (the ResolvedTopicReference). Pretty quickly I realized there were a LOT of these, even in my smaller sample project, and stepping through each iteration was kind of horrible. To work around this, I reverted to a variation on printf debugging. I started adding in code to see what was happening, and more specifically to look for what I was after – the node in the end result that represented the top-level module. My first printf debugging worked on printing the name (a String) of the entity.

if entity.name.plainText == "Voxels" {    print("FOUND IT!: \(entity.name)")}

The first run through just printed them all – and generated something just over 500 lines, so I took a bit of time and looked through them all. Sure enough, somewhere in the middle of that list (they’re not processed in alphabetical order) was the top-level module name that I was looking for.

I started tracking how and where that got created, and its properties set. I expanded my exploration code to only track what was in knownPages to see what it was providing.

for page in context.knownPages {    //print(page.absoluteString)    if page.url.absoluteString == "doc://Voxels/documentation/Voxels" {        print("PING")    }}

The knownPages is a computed property, filtering what’s stored in the context:

public var knownPages: [ResolvedTopicReference] {    return topicGraph.nodes.values        .filter { !$0.isVirtual && $0.kind.isPage }        .map { $0.reference }}

What I didn’t track at the time, and found a bit later, was that the filter statement turned out to be important. I didn’t fully understand the details of what made up a “resolved topic node”, and didn’t know what it meant to be “virtual” or not. While kind being page was fairly obvious, virtual can have a lot of meanings and implications.

In either case, when it was working correctly, the known pages included the url, and when it wasn’t, the page didn’t appear to exist. Because of that, I knew the issue was somewhere in the execution flow prior to where I was looking. I read back to see where things get set up and initialized.

The convert action sets up the context with the following code:

let context = try DocumentationContext(bundle: bundle, dataProvider: dataProvider, diagnosticEngine: diagnosticEngine, configuration: configuration)

The DocumentationContext initializer is pretty lean, deferring the more complex setup to an internal register function. I continued to trace that back further, and the register function uses and ultimately references another type: SymbolGraphLoader.

I was repeating my printf debugging by dropping in that a of code that looked for the URL I wanted to find earlier and earlier, making sure it was there (or not) as I went. As I was getting to the registerSymbols function on DocumentationContext, I realized the data types didn’t include the URL. I needed to understand what was beneath. I started off looking for name on the underlying types, and quickly found a surprise – it was always there, even when the URL wasn’t.

That’s when I clued in that filter added on knownPages. I realized the key difference wasn’t if the node existed, but how it was set up. The node always existed, and when the code was working, the isVirtual property was false. When it failed, isVirtual was true.

What the hell is isVirtual, what’s it mean, and where does it come from?

I was a bit confused and frustrated at this point. Not that I couldn’t find a comment that spelled out that ‘isVirtual’ meant that it shouldn’t be rendered, but that I just didn’t understand the implications and where it all came from, what it meant across all those contexts, and why it was needed. Turns out I didn’t really need to know all that detail, but since it was what was different, I wanted to understand.

I took a bit of time to look at the raw JSON of a symbolgraph file, and found that isVirtual comes from the compiler itself, and is carried through, for most symbol nodes. In that same process, I also realized that the symbol graph from the compiler did not have a symbol for the module itself. So something in the code that was loading the symbol graphs was adding a node for the module, and setting its value, and sometimes incorrectly. I continued to have this hypothesis that it was some wacky race condition in the code that hadn’t been spotted. And it sort of was, but at the algorithm level, not the code-threading level.

As a side note, isVirtual in a documentation node fundamentally means “don’t render this”. The idea being that it’s only there to link things together – relationships, overlays, etc.

SymbolGraphLoader and SymbolKit

The symbol graph loader takes in one or more symbol graph files, merges them all together, cleans them up a smidge, and creates the nodes needed to represent the higher level connections and relationships. While I still didn’t fully grok the isVirtual property and its implications, I knew that how it was being set for the module level node was what I cared about.

When I was looking for that code, I found the following:

private static func moduleNameFor(_ symbolGraph: SymbolGraph, at url: URL) -> (String, Bool)

I hadn’t yet joined the dots to see where it was set up, but knowing if something was a “main” symbol graph or not sounded promising. I kept that in mind while I dug further. I kept digging and found where the loader was collecting the symbol graphs, annotating them, and merging them. It SymbolGraph loader fed them all into an instance of GraphCollector. And that code is from a different library: SymbolKit.

Scanning through that code, I found the same function: moduleNameFor. Same parameters and outputs, a public symbol in SymbolKit and private in Docc. I’m guessing it started in Swift Docc, and was later extracted out into the library. The end result was identical logic in two places, so I made a note to clean that up later.

The GraphCollector turned out to be key. It holds the source for the data that’s used to determine the top-level module node.

The mergeSymbolGraph method in the graph collector pulls everything together. Within the collector, the data about the graphs are stored in dictionaries keyed by the name of the module. In addition to providing a unified graph by the module name, it also keeps track of each modules it loads, and marks it as a primary module or an extension module. The “is it the primary module graph” setting uses the logic in moduleNameFor.

In this function, you provide a loaded symbol graph and the name of the url, and it returns back the name of the module described in the graph and if it is a primary module. The key logic that makes this determination was the following line:

let isMainSymbolGraph = !url.lastPathComponent.contains("@")

The presumption when this was written was that all symbol graphs would come from the compiler, and the ones that extend an existing symbol graph will have an @ symbol in the name. Snippets blow up the assumption. The snippet-extractor code, that creates the symbol graph from snippets, names the symbol graph file YourModule-snippets.symbols.json. Because the name didn’t include an @ symbol, snippet graph files were being regarded as “primary” graphs.

Back in the DocumentationContext, there’s an extension on SymbolGraphLoader that provides the URL for the module: mainModuleURL – and this is where the flaw exhibits. The extension’s method uses first on the list of graphLocations from the collector to get the primary module, which assumes there’s only one. When more than one exists, it returns a non-deterministic result. Sometimes it was returning the one referenced from the snippet symbol graph, and other times it was the “right” symbol graph.

It’s not a direct path to easily find where and how that’s used to set up the URL. The line of code that pulls this detail:

let fileURL = symbolGraphLoader.mainModuleURL(forModule: moduleName)

uses that later to mix together the topic graph:

addSymbolsToTopicGraph(symbolGraph: unifiedSymbolGraph, url: fileURL, symbolReferences: symbolReferences, moduleReference: moduleReference)

This is what ultimately mixes in the isVirtual property into the topic graph, and that’s what sets the module to isVirtual – assuming there’s only one and using the “first” one it grabs.

Fixing the issue

The work above took place over the course of 3 days and resulted in 3 issues reported, one each to swift-docc-plugin, swift-docc, and swift-docc-symbolkit. The first of those I closed as soon as I realized it had nothing to do with the issue.

I stripped back out my printf debugging code, and in the end there were two relatively small changes that I made into pull requests – one for the fix in SymbolKit, and a supplement that just cleaned things up a bit in DocC.

I’ve proposed a solution that changes the logic in SymbolKit to support the fundamental assumptions that “there can be only one” main symbol graph. I did think about trying to represent the snippets as a different type in the collector (other than primary and extension), but I spotted a number of other places in the code that had that idea heavily built-in. They also leveraged .first() to get at the primary module, if it existed. Since snippets were added a couple of years ago, and this hadn’t been identified and debugged, I wasn’t sure what was expected for the function returning the name and processing of snippet symbol graphs. I opted for a change that tweaks the logic in SymbolKit, adding an inspecting of the isVirtual property in the metadata of the module in the symbolgraph in addition to the verifying there wasn’t an @ symbol in the filename.

I also opened a supplemental PR in docc to de-duplicate that logic and keep it all in once place. The main issue (the comments in which are a summarized, short-form of this post) looks like it’ll be fully resolved by the change in SymbolKit. But since I’d done the digging, and noticed the duplication, I figured it wouldn’t hurt to help clean things up a bit.

https://rhonabwy.com/2024/11/07/code-spelunking-in-docc/

#apple #docc #ios #swift #swiftui #technology





Show Original Post


07.11.2024 21:44
gallaugher (@gallaugher@mastodon.world)

I'm curious how #SwiftUI folks organize their #Firebase CRUD functions. It seems like I can create these as static functions inside a class, then reference the class.function whenever I want to invoke them. Any concerns with this approach? Any good examples using recent SwiftUI techniques if other method are preferred? Thanks!




Show Original Post


07.11.2024 19:41
gallaugher (@gallaugher@mastodon.world)

I was following along with @peterfriese's Build a Second Brain w/Firebase & #SwiftUI (thx, Peter!) and noticed he had some sort of Loren Ipsum generator used (around 18:10). I've not seen this before. What is it & how do I get it in Xcode? Thx!




Show Original Post


07.11.2024 18:06
pauljadam (@pauljadam@mastodon.social)

Updated the Sliders demo to meet 's Pointer Gestures success criterion that requires single tap alternatives to the gesture used to adjust the slider.

apps.apple.com/app/accessibili





Show Original Post


07.11.2024 17:29
titociuro (@titociuro@mstdn.social)

Hi fellow Apple devs! How is Xcode 16 these days? Latest beta better?

#SwiftUI #Xcode #iOSDev




Show Original Post


1 2 3 4 5 6 7 8 ...57
UP