惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
P
Palo Alto Networks Blog
C
Cisco Blogs
The Hacker News
The Hacker News
T
Threatpost
S
Schneier on Security
K
Kaspersky official blog
Spread Privacy
Spread Privacy
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
NISL@THU
NISL@THU
量子位
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google DeepMind News
Google DeepMind News
Security Latest
Security Latest
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
博客园 - 叶小钗
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
News and Events Feed by Topic
爱范儿
爱范儿
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Cisco Talos Blog
Cisco Talos Blog
GbyAI
GbyAI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
T
Tenable Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
Vulnerabilities – Threatpost
Forbes - Security
Forbes - Security
博客园 - 三生石上(FineUI控件)
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News and Events Feed by Topic
V
V2EX
Webroot Blog
Webroot Blog
The Register - Security
The Register - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Blog — PlanetScale
Blog — PlanetScale
M
MIT News - Artificial intelligence
Scott Helme
Scott Helme
Simon Willison's Weblog
Simon Willison's Weblog
L
LangChain Blog
W
WeLiveSecurity
Cloudbric
Cloudbric

Inside Nutrient

A guide to the invisible work behind documents Introducing Nutrient Documents for Salesforce: Native document generation and signing Document AI vs. traditional OCR: Choosing between OCR, AI, and hybrid pipelines PDF SDK compliance and security evaluation checklist for enterprise teams (2026) Invariant Corp replaces paper processes with Nutrient Workflow and scales without limits What is process mapping? A complete guide Nutrient vs. Conga Composer for Salesforce document generation (2026) Document routing: How to automate document distribution The CTO’s AI playbook: Why accountability architecture beats orchestration Compliance workflow automation: Why built-in compliance is table stakes Workflow diagrams: Examples, symbols, and how to build one that actually runs Digital forms: Replace paper forms with automated workflows Approval workflow software: How to automate approvals Why document-centric automation is different The CEO’s AI playbook: Why decision architecture beats model selection Nutrient SDK product updates for Q1 2026 PDF redaction verification: How to prove sensitive data is permanently removed What is a VPAT? The complete guide to accessibility conformance reports What is PDF/UA? The accessible PDF standard explained Salesforce eSignatures: Generate, sign, and track documents in one flow Online document viewer: Options, tradeoffs, and how to embed one Document viewer for web apps: React, Vue, Angular (2026) Best document viewers in 2026: A buyer’s guide How to edit a PDF in Python: Add text, images, and annotations Nutrient advances Workflow platform with agentic AI for enterprise-grade speed and consistency in document-heavy operations How to create a Salesforce quote template from opportunity data The business case for accessibility: Five ways it drives enterprise value Python PDF library comparison (2026): 7 libraries for developers Why your AI agent hallucinates PDF table data PDF.js limitations: When to upgrade to a commercial PDF SDK How Subject scaled 5× with Nutrient’s PDF SDK without rebuilding its document layer I replaced our sales training with an AI coach that runs in Slack — here’s what broke Redirecting to: https://securitybuzz.com/cybersecurity-news/why-enterprise-permissions-are-ais-most-dangerous-inheritance/ Nutrient .NET SDK vs. iText Core: Complete comparison for .NET developers DocuVieware: Support’s most frequently asked setup questions Introducing Nutrient Workflow How to convert PDF to Word in C# (.NET) When email and spreadsheets stop working: Work order approval workflows for field teams on the move Compliance with confidence: Why document-centric automation is the foundation of your mission Nutrient expands AI Assistant, automating multistep document workflows inside any application What is document generation? A developer’s guide to PDF generation Document Converter data flow and how real-time watermarks skip the queue PDF/UA compliance guide: Requirements, standards, and best practices Computers still can’t understand you How Athena Intelligence built AI agents for regulated enterprises with Nutrient’s document infrastructure How to convert HTML to PDF (2026): 4 methods from browser print to SDK How to build a document extraction pipeline with Nutrient Vision API OCR vs. intelligent document processing: Choosing the right document extraction engine Beyond OCR: How document intelligence eliminates manual processing in regulated industries Nutrient vs. IronPDF: Complete comparison for .NET developers Nutrient vs. Aspose.PDF: Complete comparison for .NET developers Redirecting to: https://fortune.com/2026/02/19/openclaw-who-is-peter-steinberger-openai-sam-altman-anthropic-moltbook/ Lufthansa Systems uses Nutrient to deliver reliable, scalable PDF rendering for pilots worldwide Nutrient vs. Syncfusion: Complete comparison for .NET developers React’s useTransition: The hook you’re probably using wrong First City Monument Bank streamlines banking processes with Nutrient Workflow Redirecting to: https://www.sdcexec.com/warehousing/automation/article/22957364/nutrient-workflow-automation-the-missing-link-in-supply-chain-efficiency The complete guide to digital signatures: PAdES, CAdES, and XAdES explained Nutrient Python SDK: Production-grade document processing for Python Introducing agentic document editing for web applications with AI Assistant Nutrient vs. QuestPDF: Complete comparison for .NET developers How we fixed the GdPicture license expiration (and what to do if you’re affected) Red team security testing with agentic AI The future of healthcare document automation Best healthcare workflow software compared Nutrient SDK product updates for Q4 2025 How Harvey scaled legal document workflows 50 percent MoM without rebuilding infrastructure HIPAA-compliant document management in hospitals How we optimized rendering performance while handling thousands of annotations in React — Part 2 Automated PII removal with Nutrient API Redirecting to: https://www.devopsdigest.com/2026-low-code-no-code-predictions Redirecting to: https://www.kmworld.com/Articles/Editorial/ViewPoints/Leaders-predict-AI-to-continue-permeating-all-aspects-of-KM-in-2026-172594.aspx What are deep agents and how do they solve complex problems? Whipping up document magic: Your easy-bake recipe for Vue and Nutrient Web SDK 🧁 What I’ve learned about product iteration planning while building SDKs Passwordless document signing: Three-layer security guide New zip folder functionality streamlines file management in Document Automation Server The keyboard shortcuts playbook: Taking control of keyboard events in Nutrient Web SDK From experienced engineer to AI beginner: My unexpected journey AI-assisted manual testing: Handling Safari’s PDF rendering and UI quirks How to keep a 20-year-old SDK up to date How we optimized rendering performance while handling thousands of annotations in React — Part 1 Nutrient announces new executive hires to accelerate next phase of growth High performance UI using web workers Automate document conversion at scale with Python and Nutrient DCS From curiosity to PLG (and AI): My journey to understanding product-led growth Prost to progress: One year as Nutrient Pigeon usage at Nutrient: Bridging native SDKs to Flutter Modernizing CI build servers: How to migrate from Chef to Ansible Unix man pages: AI-friendly documentation since 1971 Consistent hashing for even load distribution Best AI redaction APIs: Complete comparison guide for 2025 Why AI document redaction matters for modern security From coding to coordinating: How AI transformed my workflow What is intelligent document processing (IDP)? A complete guide Enterprise PDF SDKs: Best PSPDFKit (now Nutrient) alternatives Nutrient SDK product updates for Q3 2025 GdPicture support best practices Redacting sensitive data with Nutrient AI redaction API How AI is transforming the customer experience at Nutrient: From instant answers to intelligent support
Customizable SwiftUI toolbar API for developers
Stefan Kieleithner · 2024-11-05 · via Inside Nutrient

At Nutrient, our mission is to continually enhance the capabilities of our SDKs, making them more powerful and flexible for our customers to use. One of our recent focus areas has been enhancing the SwiftUI capabilities of Nutrient iOS SDK. In particular, we improved our API for customizing the main toolbar.

TL;DR

We’ve enhanced Nutrient iOS SDK with a flexible, SwiftUI-native toolbar customization API. This lets developers mix default SDK buttons (like annotations, thumbnails, and outlines) with their own, while managing state (selected, enabled, hidden) via a unified SwiftUI environment. By adopting Point-Free’s Perception library, we achieved fine-grained view updates without requiring iOS 17’s @Observable, making the SDK compatible back to iOS 15.

Handling buttons for toolbars and navigation bars is more complex in SwiftUI if we only provide UIKit APIs, as UINavigationController and UIBarButtonItem don’t interact very well with SwiftUI’s NavigationStack and toolbar modifiers.

In this blog post, I’d like to take you on a journey of investigating and finding the best approach for a flexible public main toolbar API in SwiftUI. The main toolbar, typically shown in the navigation bar on iOS, consists of action buttons for many of the built-in features Nutrient provides — like showing the annotation toolbar, presenting the document outline, and searching the document — while also allowing customers to add their own buttons and change which buttons are visible.

Main Toolbar

PDFView is the view provided by Nutrient iOS SDK for viewing and editing documents in SwiftUI apps. Our primary goal was to develop a SwiftUI API that allows developers to flexibly and intuitively modify the main toolbar shown above or below a PDFView, as we want developers to be able to mix and match their own toolbar buttons with buttons we provide. For Nutrient’s built-in buttons, we want to support customizing the visual appearance of those buttons, while also handling state — such as whether a button is selected — ourselves, so that our customers don’t need to manage this.

We explored a couple possibilities of how an API for this could look, including:

  • Adding special view modifiers to PDFView, which handle adding buttons to the toolbar.
  • Exposing View structs individually for each button, and somehow connecting them with the PDFView.

We really wanted to make use of as much of the system-provided SwiftUI APIs as possible — like the toolbar(content:) modifier to add views, including buttons, to the toolbar — while also allowing developers to add their own buttons to the toolbar easily, so we tried to go with the most flexible approach.

Challenges and initial solutions

We were looking at the latter option of exposing Views for each button individually that customers can then add anywhere they want themselves. This brought up the challenge of how to communicate between the buttons and PDFView while keeping the API surface mostly clean:

var body: some View {

NavigationStack {

PDFView(document: ...)

.toolbar {

AnnotationButton() // How to communicate from here to `PDFView` without requiring customers to implement this themselves?

}

}

}

We came up with making use of the environment in SwiftUI. To make an object available in the environment, we create a new class and then have two options:

Since our SDK supports iOS versions back to iOS 15, we can’t yet make use of the Observation framework. So we created a new type, PDFView.Scope, as an ObservableObject that has all the state for the buttons and allows PDFView to alter this state based on the document shown via @EnvironmentObject:

extension PDFView {

class Scope: ObservableObject {

struct ButtonContext {

var isEnabled: Bool = true

var isHidden: Bool = false

var isSelected: Bool = false

}

init() { }

var annotationButtonContext = ButtonContext()

var thumbnailButtonContext = ButtonContext()

// ... other button contexts

}

}

let scope = PDFView.Scope()

var body: some View {

NavigationStack {

PDFView(document: ...)

.toolbar {

AnnotationButton()

}

.environmentObject(scope)

}

}

To make the API more convenient to use, we added a view modifier API, pdfViewScope(_:), which sets an EnvironmentObject developers can place somewhere in their view hierarchy. This covers both the toolbar buttons and PDFView, so they’re able to communicate using the same environment object:

extension View {

/// Set a ``PDFView.Scope`` in the view hierarchy.

/// Any toolbar buttons and ``PDFView`` elements in the child tree

/// will be able to make use of this ``PDFView.Scope``.

public func pdfViewScope(_ pdfViewScope: PDFView.Scope) -> some View {

environmentObject(pdfViewScope)

}

}

Problem: Unnecessary rerenders

Initially, PDFView.Scope was used as an @EnvironmentObject within PDFView. This approach caused the entire PDFView to rerender whenever any property in PDFView.Scope changed, leading to performance issues and runtime warnings, as we were modifying state while a view was updating. Specifically, the “Publishing changes from within view updates is not allowed” warning indicated a fundamental problem with our state management.

One of the main advantages of @Observable from the Observation framework compared to using ObservableObject is that SwiftUI views only rerender when values change if they’re actually being used in the body of a view. So switching to @Observable would’ve been the perfect solution for us.

Solution: Adopting perception

That’s when we found the Perception(opens in a new tab) library from the folks at Point-Free. To address the issues we encountered, we explored using the @Perceptible property wrapper. This wrapper backports the @Observable functionality to earlier iOS versions, back to iOS 13, ensuring that only the views dependent on the modified properties are updated. This selective updating mechanism reduces unnecessary rerenders and improves performance, essentially eliminating the issues we ran into.

We were trying to steer clear of using Swift Package Manager (SPM) to integrate third-party dependencies, as this adds overhead to our build system for our SDK, so we tried to look into alternatives to integrate Perception. Integrating without SPM presented its own set of challenges. Macros in Swift are closely tied to SPM, requiring manual integration steps to use @Perceptible.

We started by checking out the swift-perception(opens in a new tab) repository. After building the binary using swift build -c release, we manually copied the macro binary that was created into our project directory.

Next, we updated the compiler flags to include the macro plugin executable. We added -load-plugin-executable Vendor/swift-perception/PerceptionMacros#PerceptionMacros to Other Swift Flags in the Xcode project build settings. We also copied the necessary source files and adjusted their visibility by replacing public with internal, ensuring they fit seamlessly into our project.

Creating a wrapper for PDFView.Scope

To avoid exposing APIs from the Perception library — like @Perceptible— in our API, we created a wrapper class and a property wrapper for PDFView.Scope. This encapsulation not only protects our API design, but it’ll also allow for a smoother transition to @Observable in the future without breaking the API. Therefore, the implementation of our API ended up looking something like this:

@propertyWrapper public struct Scope: DynamicProperty {

public class ID {

var internalPdfContext = InternalPDFContext()

}

@State private var value = PDFView.Scope.ID()

public init() { }

public var wrappedValue: PDFView.Scope.ID { value }

}

@Perceptible class InternalPDFContext {

struct ButtonContext {

var isEnabled: Bool = true

var isHidden: Bool = false

var isSelected: Bool = false

}

init() { }

var annotationButtonContext = ButtonContext()

var thumbnailButtonContext = ButtonContext()

// ... other button contexts

let actionEvents = PassthroughSubject<PDFView.ActionEvent, Never>()

}

With @Perceptible integrated and the PDFView.Scope encapsulated, we began prototyping the new toolbar API. The focus was on flexibility and ease of use, allowing developers to:

  • Add default SDK buttons — Developers can easily add annotation buttons, thumbnails, document editor, content editor, bookmarks, etc. next to their own custom toolbar buttons
  • Customize visual representation of buttons — Developers can modify how buttons are shown in the toolbar by providing a custom view builder.

This led us to creating the buttons in our SDK that look similar to this:

public struct AnnotationButton<Label: View>: View {

@Environment(InternalPDFContext.self) private var pdfContext: InternalPDFContext

private let label: Label

public init(@ViewBuilder label: () -> Label) {

self.label = label()

}

public var body: some View {

WithPerceptionTracking {

if !pdfContext.annotationButtonContext.isHidden {

Button {

let select = !pdfContext.annotationButtonContext.isSelected

pdfContext.actionEvents.send(.setAnnotationMode(showAnnotationMode: select, animated: true))

} label: {

label

}

.disabled(!pdfContext.annotationButtonContext.isEnabled)

.keyboardShortcut("a", modifiers: [.command, .shift])

}

else {

EmptyView()

}

}

}

}

This made use of InternalPDFContext in the environment to detect whether a button should be selected, hidden, or disabled. This decision is based on whatever PDFView using that same context in the environment sets based on the visible document.

With all these changes implemented, the usage of our final API showing a PDFView with some of our default buttons and custom buttons could look like this in practice:

@PDFView.Scope var scope

var body: some View {

NavigationStack {

PDFView(document: document)

.toolbar {

AnnotationButton()

Button("Custom") { print("Action") }

OutlineButton()

ShareButton()

ThumbnailsButton()

}

.pdfViewScope(scope)

}

}

Conclusion

By exposing a new API for customizing the toolbar, and by making use of the SwiftUI environment and Perception and encapsulating PDFView.Scope, we improved the experience of using SwiftUI with Nutrient. This new approach ensures a smoother developer experience by enabling developers to customize the toolbar in a very SwiftUI-native way. Our customizable toolbar API allows developers to tailor the PDF viewer’s toolbar to their specific needs, changing the visual representation of buttons, and using their own buttons next to Nutrient’s buttons.

FAQ

The toolbar API works with iOS 15 and above. Thanks to Perception, you get @Observable-like behavior on older versions.

No. We embedded the Perception macros manually to avoid requiring SPM, so integration is seamless.

Yes. Just use .toolbar and place SDK and custom buttons together in your SwiftUI view hierarchy.

Absolutely — each SDK button accepts a @ViewBuilder, allowing you to fully customize its appearance and behavior.

The SDK buttons won’t function correctly, as they won’t share environment state with PDFView. This modifier is required for proper state synchronization.

Yes. Our architecture is designed to support a seamless transition to native @Observable once iOS 17+ adoption becomes widespread.