A preview of DocC's support for combined documentation

Hello,

I'm happy to let you know that there's now enough pieces of combined documentation of multiple targets in place across Swift-DocC, Swift-DocC Render, and the Swift-DocC Plugin to enable you to try out an early, and still experimental, version of this feature for the use-case of hosting combined documentation for a Swift package together.

I've created a tiny Swift package example that hosts its combined documentation on GitHub Pages as a preview of what's supported today with the latests Swift-DocC Plugin version (1.4.2) and the Swift-DocC and Swift-DocC Render in the latest main Swift development toolchain. Below is a screenshot of one of those pages:

A few things you may notice on this page:

  • The navigation hierarchy on the left-hand side displays the hierarchies for both targets in this package. This means that Quick Navigation—activated via the / key/button—can navigate to pages in both targets.
  • The top of the navigation hierarchy, displays the name of this Swift package. Clicking on this element takes you to a minimal synthesized landing page for these targets. The same page can be reached at /documentation.
  • The content of this page (from the "Outer" target) contains authored symbol links to pages from the "Inner" target. These symbol links are written with a leading slash followed by the module name and the rest of the path to the symbol. For example: /Inner/InnerClass.
  • The "Inherits From" and "Conforms To" symbol relationships are automatically turned into links to those symbols (from the other target). If you navigate to someProperty or someFunction() you may also notice that the InnerValue elements in their declarations is a link to that page.

Try this yourself

Building combined documentation for multiple targets with the Swift-DocC Plugin is easy. When you add the new --enable-experimental-combined-documentation flag to your existing swift package generate-documentation call and specify one or more targets using --target <target-name> or --product <product-name> , the plugin will build all the specified targets, configure them to support linking to their target dependencies' documentation, and output a documentation archive with their combined documentation.

If you're using a main Swift development toolchain you'll get a combined navigation hierarchy and a minimal synthesized landing page, like in the example above. You can download a Swift toolchain for your platform and follow the instructions on that page or use the Swiftly CLI to install a Swift development toolchain. You can use the development toolchain to build documentation with the Swift-DocC plugin by passing the toolchain path to the swift package CLI:

swift package                                   \
  --toolchain /path/to/the-toolchain            \
  generate-documentation                        \
  --enable-experimental-combined-documentation  \
  ...

You can also use a Swift 6.0 toolchain but if you do, each target will get a separate navigation hierarchy and you won't have any landing page. You can still write links from targets to their dependencies and symbol relationships are still automatically turned into links. Note: the Swift 6.0 toolchain won't receive any further updates related to combined documentation.

We'd love for you to try this on your Swift packages and give us your feedback on what's already there, suggest enhancements, and help us find bugs. Note that this feature is experimental and may receive breaking changes.

One feature that's mentioned in the combined documentation of multiple targets thread which isn't available yet is the ability to write your own landing page and package-level articles (that aren't about to any particular target). We have some ideas for how we want to do this but we want to discuss those ideas—in a separate, future Forums thread—and wanted to let you try out what's already available before that.

21 Likes

I have trouble getting some backreferences to work.

I made these changes to this example project, and then compiled the web documentation.

jaanus@jk-mbp ~/D/combined-documentation-example (main)> git diff
diff --git a/Sources/Inner/Inner.swift b/Sources/Inner/Inner.swift
index 2741ffe..4724b64 100644
--- a/Sources/Inner/Inner.swift
+++ b/Sources/Inner/Inner.swift
@@ -23,6 +23,8 @@
 public protocol InnerProtocol {}
 
 /// A class in the inner target.
+///
+/// See also OuterClass: ``/Outer/OuterClass``
 open class InnerClass {}
 
 /// A public value in inner target.
diff --git a/Sources/Outer/Outer.docc/Outer.md b/Sources/Outer/Outer.docc/Outer.md
index 765cd01..9eb594b 100644
--- a/Sources/Outer/Outer.docc/Outer.md
+++ b/Sources/Outer/Outer.docc/Outer.md
@@ -1,3 +1,5 @@
 # ``Outer``
 
 The outer module of this combined-documentation example.
+
+See also the Inner class: ``/Inner/InnerClass``

The change to Outer is rendered as expected:

However, the change to InnerClass is not rendered as expected:

Could this be because Outer has Inner defined as a dependency in Package.swift, but not vice versa? So the Inner documentation generation does not have access to Outer symbols, and thus won’t render links to those.

Correct. Each target's documentation can only link to its direct dependencies' documentation. This is described in the original pitch post and there's also a section that talks about what some of the tradeoffs would be of allowing bidirectional links and how that creates an inconsistent experience if one of the targets can be imported individually.

Note that you wouldn't be able to configure the "Inner" target to depend on the "Outer" target because the code doesn't allow for cyclic dependencies (not documentation specific).

1 Like

We've yet to see many practical use cases for linking to specific symbols "up" the documentation hierarchy (for example inferred from the target's source dependency hierarchy). A few examples have been brought up but we felt that there were better ways to write that documentation which didn't involve bidirectional links. For example:

  • The inner target that wants to point to an example implementation for conforming to a protocol.

    Here the conforming type's documentation would repeat the protocol's documentation. It would likely be more helpful to the reader to include specific implementation examples as inline code blocks or link directly to the relevant source code of the conforming type, rather than its documentation. Because that wouldn't link to documentation, it would be a regular https link and not a symbol link in DocC.

  • A package's top-level documentation wants to point to a list of consumers.

    This documentation is most likely to only link to the top-level pages of the consuming packages, not individual pages in their documentation. In this case, cloning and building the consuming packages documentation for a single top-level link may not be worth it compared to a regular https link.

  • Documentation about how to use an outer symbol and an inner symbol together.

    If this documentation is a bit higher level and/or covers more than a single symbol in either target, that could be an indication that it's better suited for an article than as documentation for any specific symbol. That article could exist in:

    • the outer target
    • the inner target
    • package-level documentation

    An article in the inner target has the same problems with bidirectional links as a symbol in the inner target would. However, both the outer target and the package-level documentation are able to link to symbol in both targets. Either the outer target or the package-level documentation is also likely a better conceptual place for this documentation.

    If a consumer of the package is able to import only the inner target, it's also a bit odd if that target has documentation about API that's not available to the consumer.

    On the other hand, if this documentation is very specific to a symbol in either the outer target or the inner target, that could be an indication that it's better suited as documentation for that specific symbol, rather than an article.

    If it's specific to the outer API, then the documentation can link to documentation for both targets. If, however, it's specific to the inner API, then it can't link to the outer API.

    One key question to ask here is if a consumer can import only the inner target. If they can, then the documentation would probably be more helpful to them if it focused on how the consumer's own code needs to interact with the inner API, drawing from the outer API's implementation as code examples (the first example use case)

  • Packages with one or more example projects

    Currently, the preferred way for packages with dedicated example projects to document those example projects is to write a single article that describe what that example project showcases, any specific configuration or setup needed to run it, and possibly describe some of its implementation. This article would use a @PageKind directive to change its style from an "article" to a "sample code page" and a @CallToAction button to give readers a prominent button to check out that example project.

    In this case the article is a part of the target's documentation so links to the target's pages are local. In this case there are no other pages specific to the example project to link to.

    We can see a possible future use case for larger example projects that have many pages of documentation about the example itself, maybe even documentation about its individual symbols. However, we consider this use case to be slightly different from linking between other targets in a package because the example project is not "API" for the consumer to use. This has a couple of implications:

    • It's would "pollute" the navigation hierarchy and features like Quick Navigation if symbols from the example project displayed alongside the API documentation.
    • The example project is likely to link to many pages in the target/package documentation but the only page that has a reason to link to specific symbols in the example project's documentation is the example project's "sample code page" with the call-to-action.

    This means that there are additional aspects about combined documentation—other than unidirectional links—that isn't a good for example projects.

    We don't have any specific plans for large example projects with their own documentation hierarchies, but if we were to consider it in the future it's possible that we would treat it like it as a special case with multiple unique behaviors that don't apply to "general" combined documentation.

2 Likes

Hello @ronnqvist I did tried it and it's crashing with the toolchain (swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain).

swift package \
  --toolchain "/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain" \
  --allow-writing-to-directory "../docs/develop" \
  generate-documentation \
  --target VitaminPlay \
  --target AccentColor \
  --target Accordion \
  --target ArticleCard \
  --target Assets \
  --target Button \
  --target Carousel \
  --target Checkbox \
  --target Chip \
  --target DesignTokens \
  --target Divider \
  --target Fonts \
  --target IconButton \
  --target Link \
  --target Loader \
  --target Price \
  --target ProductCard \
  --target ScoreRating \
  --target Shared \
  --target StarRating \
  --target Sticker \
  --target TabView \
  --target TextEditor \
  --target TextField \
  --target VitaminToggle \
  --output-path "../docs/develop" \
  --transform-for-static-hosting \
  --enable-experimental-combined-documentation
Generic signature: <Ď„_0_0, Ď„_0_1 where Ď„_0_0 : View, Ď„_0_1 : Downloader, Ď„_0_1.Id == URL, Ď„_0_1.Value == Image>
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.
Pass '-Xfrontend -disable-round-trip-debug-types' to disable this assertion.
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.
Stack dump:
0.	Program arguments: /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend -frontend -c -primary-file /Users/mac-JMACKO01/Developer/generate_doc/code/Sources/Components/Carousel/Carousel+CardContent.swift /Users/mac-JMACKO01/Developer/generate_doc/code/Sources/Components/Carousel/Carousel.swift /Users/mac-JMACKO01/Developer/generate_doc/code/Sources/Components/Carousel/Debug/CardContentDebugOptions.swift /Users/mac-JMACKO01/Developer/generate_doc/code/Sources/Components/Carousel/Models/CardContentViewModel.swift /Users/mac-JMACKO01/Developer/generate_doc/code/Sources/Components/Carousel/Models/CardModel.swift /Users/mac-JMACKO01/Developer/generate_doc/code/Sources/Components/Carousel/Models/CardViewModel.swift /Users/mac-JMACKO01/Developer/generate_doc/code/Sources/Components/Carousel/Models/Progress.swift -emit-dependencies-path /Users/mac-JMACKO01/Developer/generate_doc/code/.build/arm64-apple-macosx/debug/Carousel.build/Carousel+CardContent.d -emit-reference-dependencies-path /Users/mac-JMACKO01/Developer/generate_doc/code/.build/arm64-apple-macosx/debug/Carousel.build/Carousel+CardContent.swiftdeps -target arm64-apple-macosx11.0 -Xllvm -aarch64-use-tbi -enable-objc-interop -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.0.sdk -I /Users/mac-JMACKO01/Developer/generate_doc/code/.build/arm64-apple-macosx/debug/Modules -I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib -F /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks -color-diagnostics -enable-testing -g -debug-info-format=dwarf -dwarf-version=4 -module-cache-path /Users/mac-JMACKO01/Developer/generate_doc/code/.build/arm64-apple-macosx/debug/ModuleCache -swift-version 5 -Onone -D SWIFT_PACKAGE -D DEBUG -empty-abi-descriptor -Xcc -working-directory -Xcc /Users/mac-JMACKO01/Developer/generate_doc/code -resource-dir /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/lib/swift -enable-anonymous-context-mangled-names -file-compilation-dir /Users/mac-JMACKO01/Developer/generate_doc/code -Xcc -isysroot -Xcc /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.0.sdk -Xcc -F -Xcc /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks -Xcc -fPIC -Xcc -g -module-name Carousel -package-name code -in-process-plugin-server-path /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/lib/swift/host/libSwiftInProcPluginServer.dylib -plugin-path /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/lib/swift/host/plugins -plugin-path /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/local/lib/swift/host/plugins -target-sdk-version 15.0 -target-sdk-name macosx15.0 -external-plugin-path /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib/swift/host/plugins#/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/bin/swift-plugin-server -external-plugin-path /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/local/lib/swift/host/plugins#/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/bin/swift-plugin-server -parse-as-library -o /Users/mac-JMACKO01/Developer/generate_doc/code/.build/arm64-apple-macosx/debug/Carousel.build/Carousel+CardContent.swift.o -index-store-path /Users/mac-JMACKO01/Developer/generate_doc/code/.build/arm64-apple-macosx/debug/index/store -index-system-modules
1.	Apple Swift version 6.1-dev (LLVM 89ccf4b8a46135a, Swift 6a5ae8d5df144dd)
2.	Compiling with effective version 5.10
3.	While evaluating request IRGenRequest(IR Generation for file "/Users/mac-JMACKO01/Developer/generate_doc/code/Sources/Components/Carousel/Carousel+CardContent.swift")
4.	While emitting IR SIL function "@$s8Carousel11CardContentV4body7contentQr7SwiftUI014_ViewModifier_C0VyACyxq_GG_tFAF0H0PAFE8onChange2of7performQrqd___yqd__ctSQRd__lFQOyAF08ModifiedC0VyAlFEAmnOQrqd___yqd__ctSQRd__lFQOyAlFEAmnOQrqd___yqd__ctSQRd__lFQOyAlFEAmnOQrqd___yqd__ctSQRd__lFQOyAlFE0J6AppearAOQryycSg_tFQOyAlFE7overlay_9alignmentQrqd___AF9AlignmentVtAfKRd__lFQOyAlFEAT_AUQrqd___AWtAfKRd__lFQOyAL6SharedE2if_9transformQrSbyXK_qd__xXEtAfKRd__lFQOyAlFE5frame5width6heightAUQr12CoreGraphics7CGFloatVSg_A5_AWtFQOyAlFE7gesture_9includingQrqd___AF11GestureMaskVtAF7GestureRd__lFQOyAlFE10preference3key5valueQrqd__m_5ValueQyd__tAF13PreferenceKeyRd__lFQOyAlFE8position1x1yQrA4__A4_tFQOyAlFEA_A0_A1_AUQrA5__A5_AWtFQOyAlFE0U6EffectyQrSo17CGAffineTransformVFQOyAF6ZStackVyAF05TupleH0VyAF7ForEachVySaySiGSiAlFE0J9DisappearAOQrAS_tFQOyAlFEArOQrAS_tFQOyAlFE0J10TapGesture5countAOQrSi_yyctFQOyAlFEA17_A18_A19_QrA4__A4_tFQOyAlFEA_A0_A1_AUQrA5__A5_AWtFQOyAlXEAY_AZQrSbyXK_qd__xXEtAfKRd__lFQOyAlFEAT_AUQrqd___AWtAfKRd__lFQOyAC0bH033_52A3C2B362B4C7D5ECC1F72FBFD8C439LLVyxq__G_AF5GroupVyAF012_ConditionalC0VyAlFE6border_A0_Qrqd___A4_tAF10ShapeStyleRd__lFQOyAlFEA_A0_A1_AUQrA5__A5_AWtFQOyAlFE4fontyQrAF4FontVSgFQOyAlFE10background_AUQrqd___AWtAfKRd__lFQOyAF4TextV_AF5ColorVQo__Qo__Qo__A51_Qo_AF05EmptyH0VGGQo__AlFE6zIndexyQrSdFQOyAlFE11scaleEffect_6anchorQrA4__AF9UnitPointVtFQOyA60__Qo__Qo_Qo__Qo__Qo__Qo__Qo__Qo_SgGSg_A28_ySnySiGSiAlFEA17_A18_A19_QrA4__A4_tFQOyAlFEA_A0_A1_AUQrA5__A5_AWtFQOyAF5ShapePAFE6stroke9lineWidthQrA4__tFQOyAF9RectangleV_Qo__Qo__Qo_GSgA28_yA77_SiAlFE16allowsHitTestingyQrSbFQOyAlFEA61_yQrSdFQOyAlFEA17_A18_A19_QrA4__A4_tFQOyAlFEA_A0_A1_AUQrA5__A5_AWtFQOyAF6VStackVyA26_yAlFEA_A0_A1_AUQrA5__A5_AWtFQOyA79_AFE4fill_5styleQrqd___AF9FillStyleVtAFA42_Rd__lFQOyA83__A51_Qo__Qo__A49_tGG_Qo__Qo__Qo__Qo_GSgtGG_Qo__Qo__Qo__AX21ReadSizePreferenceKeyVQo__AF13_EndedGestureVyAF15_ChangedGestureVyAF19GestureStateGestureVyAF11DragGestureVAC15GestureProgressA34_LLOyxq__GGGGQo__Qo__AlFE7clipped11antialiasedQrSb_tFQOyAlFE0E5Shape_6eoFillQrqd___SbtAFA78_Rd__lFQOyA129__A83_Qo__Qo_Qo__AlFEA89_yQrSbFQOyA38_yA40_yAlFEA47__AUQrqd___AWtAfKRd__lFQOyA91_yA26_yA49__A49_A49_tGG_A51_Qo_A57_GG_Qo_Qo__A38_yA40_yAlFEA89_yQrSbFQOyA97__Qo_A57_GGQo__Qo__SiQo__SiQo__A4_Qo_AX17AnimationCompleteVyA4_GG_A29_Qo_AF13GeometryProxyVcfU_".
 for expression at [/Users/mac-JMACKO01/Developer/generate_doc/code/Sources/Components/Carousel/Carousel+CardContent.swift:204:20 - line:410:5] RangeText="{ proxy in
      // Carousel is represented here with e `ZStack` containing all the cards that is moved
      // horizontally with drag gestures or animated to a particular card.
      ZStack {
        /// Ignore view if there are no items.
        if numberOfItems > 0 {
          /// The `wideIndex` refers to the index when loop is enabled for wrapped around cards
          /// that are "out of rage" e.g. in the negative or grater than the card count.
          ForEach(visibleIndexes, id: \.self) { wideIndex in
            let index = convertLoopIndexToInRange(wideIndex, count: numberOfItems)

            // This check is for when changing the `numberOfItems` when things go our of sync for a
            // short while.
            if index < cardContentViewModel.cardViewModels.count {
              // This is the individual card view.
              CardView(cardViewModel: cardContentViewModel.cardViewModels[index],
                       placeholderView: placeholderView,
                       debugOptions: debugOptions)
              .overlay(
                Group {
                  if debugOptions.showWideIndex {
                    Text("\(wideIndex)")
                      .foregroundColor(Color.black) // Use `.foregroundStyle` in iOS 15.
                      .background(Color.white.opacity(0.5))
                      .font(.body)
                      .frame(height: .infinity)
                      .border(.orange)
                  } else {
                    EmptyView()
                  }
                }
                , alignment: .top)
              .if(debugOptions.disableScale == false) {
                // Scaling effects for selected index.
                $0
                  .scaleEffect(selectedIndex == wideIndex ? 2 : 1)
                  .zIndex(zIndex(forCardIndex: wideIndex))
              }
              .frame(width: cardLength, height: cardLength)
              // position uses the top leading position.
              .position(
                x: CGFloat(wideIndex) * cardLength + cardLength/2,
                y: cardLength/2
              )
              .onTapGesture {
                // Tap on a visible card to jump (animate) to it.
                jump(toIndex: wideIndex)
              }
              .onAppear { progress?(Progress(index: index, state: .onAppear)) }
              .onDisappear { progress?(Progress(index: index, state: .onDisappear)) }
            }
          } // ForEach
        }

        if debugOptions.showDebugBorders {
          ForEach(0..<numberOfItems, id: \.self) { index in
            Rectangle()
              .stroke(lineWidth: 2)
              .frame(width: cardLength, height: cardLength)
              .position(
                x: CGFloat(index) * cardLength + cardLength/2,
                y: cardLength/2
              )
          }
        }

        if debugOptions.showRulers {
          // This debug option shows a ruler with offset values for each card.
          ForEach(0..<numberOfItems, id: \.self) { index in
            VStack {
              Rectangle()
                .fill(.blue)
                .frame(width: 2, height: cardLength * 1.5)

              Text(String(format: "%.02f", -1 * CGFloat(index) * cardLength))
                .font(.caption)
            }
            .frame(width: 50, height: cardLength * 2)
            .position(
              x: CGFloat(index) * cardLength + cardLength/2,
              y: cardLength/2
            )
            .zIndex(10)
            .allowsHitTesting(false)
          }
        }
      } // ZStack
      // Groups the geometry so all the positions are moved at once. Use `.geometryGroup()` when
      // target is in iOS 17.
      .transformEffect(.identity)
      // The size of the `ZStack` is the with of all the cards.
      .frame(width: cardLength * CGFloat(numberOfItems), height: cardLength, alignment: .leading)
      .position(
        // Position is based around the center of the view, move it to be the offset minus half the
        // width plus half the view the ZStack sits inside plus half a card width.
        x: offset + centerOffset
           + (cardLength * CGFloat(numberOfItems))/2
           + proxy.size.width/2 - cardLength/2,
        y: proxy.size.height/2
      )
      // So the proxy size is stored.
      .preference(key: ReadSizePreferenceKey.self, value: proxy.size)
      .gesture(DragGesture()
        // Using `updating` to get the "gesture started" events.
        .updating($gestureState) { value, state, transaction in
          switch state {
            case .inactive:
              state = .started

              let startingOffset = offset(fromIndex: selectedIndex, withOffset: value.translation.width)
              // TODO: Modifying state during view update, this will cause undefined behavior.
              offsetAtDragStart = startingOffset
              print("drag started offset: trans \(value.translation.width), "
                    + "index \(selectedIndex), "
                    + "offset \(startingOffset) == \(offsetAtDragStart). thread: \(Thread.current)")
            case .started:
              state = .changed
            case .changed:
              break
          }
        }
        .onChanged { dragGestureChanged(translationX: $0.translation.width) }
        .onEnded { dragGestureEnded(translationX: $0.translation.width)}
      ) // gesture
      // Use the zoomed card height for the width.
      .frame(width: proxy.size.width, height: cardLength * 2)
      .if(debugOptions.clipViews) {
        $0
          .contentShape(Rectangle()) // Blocks hit testing
          .clipped() // Clips the view
      }
      .overlay(
        Group {
          // Debug view to show the state of the visible and pre-fetched cards.
          if debugOptions.showFetchDebugView {
            VStack {
              Text(.init(debugIndexesString()))
              Text("Length: \(Int(cardLength))")
              Text("offset: \(Int(offset))")
            }
            .background(Color.white.opacity(0.85))
          } else {
            EmptyView()
          }
        }
        .allowsHitTesting(false)
        , alignment: .top)
      .overlay(
        Group {
          // Line for the debug ruler option.
          if debugOptions.showRulers {
            Rectangle()
              .fill(.black)
              .frame(width: 1, height: cardLength * 3)
              .allowsHitTesting(false)
          } else {
            EmptyView()
          }
        }
      )
      .onAppear {
        print("updateVisibleIndexesAtRest - onAppear")
        // Update the visible cards when the view first appears.
        updateVisibleIndexesAtRest()
      }
      // TODO: iOS 17 use `.onChange(of: selectedIndex) { oldValue, _ in }`
      .onChange(of: numberOfItems) { newValue in
        // If the number of items changes we need to update the collection of Card View Models.
        updateCardViewModels(numberOfItems: newValue)
      }
      .onChange(of: selectedIndex) { [selectedIndex] newValue in
        // Keep the old index up to date.
        oldIndex = selectedIndex
      }
      .onChange(of: offset) { [offsetAtDragStart] newValue in
        // Update on change of `offset` so new visible cards are shown when dragging.
        if debugOptions.updateOnOffset && updateVisible {
          let range = min(offsetAtDragStart, newValue)...max(offsetAtDragStart, newValue)
          print("updateVisibleIndexesAtRest - onChange(of: offset) range: \(range) "
                + "... offsetAtDragStart: \(offsetAtDragStart)")
          visibleIndexes = calculateVisibleIndexes(selectedIndex: selectedIndex,
                                                   offsetRange: range,
                                                   numberOfItems: numberOfItems,
                                                   itemWidth: cardLength,
                                                   viewWidth: viewSize.width,
                                                   centerOffset: centerOffset)
        }
      }
      .onAnimationCompleted(for: offset) {
        // Update the visible cards at the end of the animation for `offset`. This isn't always at
        // the end of a gesture.
        if debugOptions.updateOnAnimationComplete {
          print("updateVisibleIndexesAtRest - onAnimationCompleted")
          // Updates on drag (instead of `.onChange(of: dragOffset)`), as well as when a on tap
          // animation has finished.
          offsetAtDragStart = offset(fromIndex: selectedIndex, withOffset: 0)
          updateVisibleIndexesAtRest()
        }
      }
      .onChange(of: visibleIndexes) { [visibleIndexes] newVisibleIndexes in
        print("\(visibleIndexes) => \(newVisibleIndexes)")
        print("fetching: \(fetchableIndexes)")
        // When the visible cards change, fetch content for all the necessary cards.
        cardContentViewModel.fetchCardModels(fetchableIndexes: fetchableIndexes)
      }

    "
 #0 0x0000000105e1782c (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x10572382c)
 #1 0x0000000105e15ff8 (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x105721ff8)
 #2 0x0000000105e17e74 (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x105723e74)
 #3 0x0000000195da6584 (/usr/lib/system/libsystem_platform.dylib+0x18047a584)
 #4 0x0000000195d75c20 (/usr/lib/system/libsystem_pthread.dylib+0x180449c20)
 #5 0x0000000195c82a30 (/usr/lib/system/libsystem_c.dylib+0x180356a30)
 #6 0x0000000105f56408 (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x105862408)
 #7 0x0000000100e59820 (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x100765820)
 #8 0x0000000100e59b6c (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x100765b6c)
 #9 0x0000000100e523bc (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x10075e3bc)
#10 0x0000000100e94718 (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x1007a0718)
#11 0x0000000100e93e2c (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x10079fe2c)
#12 0x0000000100d2dcc0 (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x100639cc0)
#13 0x0000000100e44aec (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x100750aec)
#14 0x0000000100e93390 (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x10079f390)
#15 0x0000000100e4d45c (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x10075945c)
#16 0x0000000100e469ec (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x1007529ec)
#17 0x000000010093f2a4 (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x10024b2a4)
#18 0x000000010093bc2c (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x100247c2c)
#19 0x000000010093b08c (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x10024708c)
#20 0x000000010094749c (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x10025349c)
#21 0x000000010093cf58 (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x100248f58)
#22 0x000000010093c624 (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x100248624)
#23 0x000000010072ac50 (/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-09-25-a.xctoolchain/usr/bin/swift-frontend+0x100036c50)
#24 0x00000001959eb154

I did fill a feedback FB15344007 if you want to test this on your side.

Hope it helps.

With the information that's available to me this I'm not sure if this is a documentation specific error or not.

Does a regular build of those same targets with that toolchain succeed or does it encounter the same error?

1 Like

Am I right that this won't work for swift packages for iOS? I'm getting the error - error: no such module 'UIKit' because the code includes UIKit imports. If so, will there be support for combined documentation using xcodebuild docbuild at some point?

While it's a completely reasonable question, xcodebuild docbuild isn't something that the working group can (or will) answer - since it's entirely dependent on internal Apple development priorities, which aren't shared.

Some features in DocC (snippets, in particular) are missing support in docbuild, even though they've been available in the public OSS toolchains for over three years. That's a fairly complete feature, where mixed synthesis of iOS, macOS, and other platform symbols is something technically possible, but essentially hand crafted today and not something that's "easily" supported in even the open-source available workflows (docc-plugin, etc).

1 Like

Thanks @Joseph_Heck for the context on xcodebuild docbuild vs. swift package. Yeah, our large enterprise iOS app environment has been migrating our internal docs to DocC, and combined documentation/top-level landing page would be a huge benefit. Unfortunately, many of our libraries depend on UIKit so looks like a non-starter at the moment. Appreciate the work you and @ronnqvist are doing on this - I tried building the docs for the tiny example linked above and it worked great!

1 Like

I'm also interesting in xcodebuild docbuild support given that current swift package seems to only build macos documentation. We're currently unable to build our documentation using it swift package due to error: Several Errors related to macos version · Issue #100 · swiftlang/swift-docc-plugin · GitHub so looking for an alternative solution to combine documentation from multiple targets/modules.

More specifically, swift build always builds for the host operating system on which it's running, with the exception of the cross-compilation targets. The DocC system works from whatever swift build generates. Apple doesn't provide an easy path to build for iOS (or its other platforms) without using Xcode.

The key here is getting the symbol graphs from the compilations to other targets (for example, iOS), after which you can merge/mix/etc to get the various graphs together. Apple's doing that for their internal documentation, but it's not a capability of the docc-plugin, at least at this point. docc-plugin is more or less a coordinator over the builds, extracting the relevant symbol graphs from the compiler, and then hands them to the raw DocC executable, which is the bit that's responsible for actually mixing those together with a documentation catalog to generate the completed archive.

Thanks! Our issue is the same as @colbrew. Our large app includes several modules, but also depends on UIKit for some of them. If the swift package plugin doesn't support this, then we have to resort to docbuild but docbuild doesn't support the combined documentation being discussed here.

It sounds like it is possible to still get what we want using docbuild, but it's unclear what all the pieces are and if there are any examples of the right approach for bringing all the pieces together.

1 Like