How to make attributed TextView for macOS and iOS with SwiftUI

Issue #956 macOS import Foundation import SwiftUI import AppKit struct AttributedTextView: NSViewRepresentable { @Binding var attributedText: NSAttributedString var isEditable: Bool = true final class Coordinator: NSObject { let parent: AttributedTextView init( parent: AttributedTextView ) { self.parent = parent super.init() } } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } func makeNSView(context: Context) -> NSScrollView { let view = NSTextView.scrollableTextView() if let textView = view.documentView as? NSTextView { textView.font = NSFont.preferredFont(forTextStyle: ....

December 7, 2023 路 2 min 路 Khoa Pham

How to show anchor bottom view in SwiftUI

Issue #954 From iOS 15, there鈥檚 a handy safeAreaInset that allows us to place additional content extending the safe area. Shows the specified content beside the modified view. safeAreaInset allows us to customize which edge and alignment we can place our views. This works for both ScrollView, List and Form and you can apply it multiple times. The content view is anchored to the specified horizontal edge in the parent view, aligning its vertical axis to the specified alignment guide....

November 22, 2023 路 2 min 路 Khoa Pham

How to drag multiple files in SwiftUI on Mac

Issue #951 Create a custom NSView that handles mouseDragged to beginDraggingSession struct DraggingSourceViewWrapper: NSViewRepresentable { let fileUrls: [URL] let onEnd: () -> Void func makeNSView(context: Context) -> DraggingSourceView { let view = DraggingSourceView(fileUrls: fileUrls) view.onEnd = onEnd return view } func updateNSView(_ view: DraggingSourceView, context: Context) { view.onEnd = onEnd } } final class DraggingSourceView: NSView { let fileUrls: [URL] var onEnd: (() -> Void)? init(fileUrls: [URL]) { self.fileUrls = fileUrls super....

November 13, 2023 路 2 min 路 Khoa Pham

How to store Codable in AppStorage

Issue #949 AppStorage and SceneStorage accepts RawRepresentable where value is Int or String. Creates a property that can read and write to a string user default, transforming that to RawRepresentable data type. init(wrappedValue:_:store:) init( wrappedValue: Value, _ key: String, store: UserDefaults? = nil ) where Value : RawRepresentable, Value.RawValue == String One clever thing (that does not work) is to use a custom Codable type that conforms to RawRepresentable, like below...

October 3, 2023 路 2 min 路 Khoa Pham

How to update widget for iOS 17

Issue #948 iOS 17 has a new Stand by mode so SwiftUI introduces containerBackground for the system to decide when to draw background. It also automatically applies margin to widget so we may need to disable that To update existing widgets, we can write some useful extension extension View { @ViewBuilder func safeContainerBackground(@ViewBuilder content: () -> some View) -> some View { if #available(iOS 17.0, *) { self.containerBackground(for: .widget, content: content) } else { self....

October 2, 2023 路 1 min 路 Khoa Pham

How to make tag flow layout using Layout protocol in SwiftUI

Issue #944 SwiftUI in iOS 16 supports Layout protocol to arrange subviews We need to implement 2 methods sizeThatFits(proposal:subviews:cache:) reports the size of the composite layout view. placeSubviews(in:proposal:subviews:cache:) assigns positions to the container鈥檚 subviews. We will make a custom layout that arranges elements from top leading and span element to new rows if it can鈥檛 fit the current row. Start by defining a FlowLayout struct that conforms to Layout protocol....

August 22, 2023 路 3 min 路 Khoa Pham

How to use hover annotation in Swift Charts

Issue #943 In this tutorial, we鈥檒l learn how to use Swift Charts to visualize ranking data. We use default AxisMarks and AxisMarks to let Swift Charts interpolate x and y grid lines. For y axis, I want to have finer grain control over the grid, so I specify an arrayLiteral Note that for rank, the lower value the better ranking, so we make use of chartYScale with reversed automatic domain to flip from 0 -> 100 to 100 -> 0...

August 18, 2023 路 2 min 路 Khoa Pham

How to scale image fill without affect layout in SwiftUI

Issue #939 Instead of letting the Image decide the size, we can put it as background or overlay. I use clipped and contentShape to avoid the fill image obscuring touch event Color.clear .frame(height: 100) .overlay { AsyncImage(url: item.imageUrl) { image in image .resizable() .scaledToFill() } placeholder: { ProgressView() } } .clipped() .contentShape(Rectangle())

July 30, 2023 路 1 min 路 Khoa Pham

How to use AppIntents in iOS 16

Issue #930 AppIntents Declare AppShortcutsProvider, note that appShortcuts uses @AppShortcutsBuilder syntax import AppIntents struct OurShortcutsProvider: AppShortcutsProvider { static var shortcutTileColor: ShortcutTileColor = .lightBlue @AppShortcutsBuilder static var appShortcuts: [AppShortcut] { AppShortcut(intent: AskIntent(), phrases: [ "Hey Joy", "Ask \(.applicationName)" ]) } } We can create an app intent in code import AppIntents import OpenAI struct AskIntent: AppIntent { static var title: LocalizedStringResource = "Hey Joy" static var description: IntentDescription = "Ask me anything" @Parameter(title: "Prompt") var prompt: String?...

July 11, 2023 路 1 min 路 Khoa Pham

How to press and hold button in SwiftUI

Issue #929 We can use ButtonStyleConfiguration to detect isPressed state struct RecordButton: View { var body: some View { Button { } label: { Image(systemSymbol: .micFill) } .buttonStyle(RecordButtonStyle()) } } private struct RecordButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .background { if configuration.isPressed { Circle() .fill(Color.pink) .frame(square: 30) } } .onChange(of: configuration.isPressed) { isPressed in print("isPressed", isPressed) } } } Alternatively, you can just use a DragGesture...

July 10, 2023 路 1 min 路 Khoa Pham

How to use NavigationSplitView and NavigationStack in SwiftUI

Issue #924 Note Navigation state needs to be in the container of NavigationSplitView for changes to propagate Need to use WindowGroup for navigation bar to work NavigationSplitView the navigation split view coordinates with the List in its first column, so that when people make a selection, the detail view updates accordingly. Programmatic changes that you make to the selection property also affect both the list appearance and the presented detail view...

June 30, 2023 路 1 min 路 Khoa Pham

How to style NavigationLink in macOS

Issue #923 NavigationLink on Mac applies the default button style. We can style it using ButtonStyle, here to use plain style we can just NavigationLink(value: DetailRoute.books) { BooksView() } .buttonStyle(.plain)

June 28, 2023 路 1 min 路 Khoa Pham

SwiftUI EnvironmentValues

Issue #922 EnvironmentValues Views in SwiftUI can react to configuration information that they read from the environment using an Environment property wrapper Updated for iOS 17

June 21, 2023 路 1 min 路 Khoa Pham

How to make TextField Stepper in SwiftUI

Issue #921 Use HStack with TextField and a little extension extension Binding where Value == Int { var toString: Binding<String> { Binding<String>( get: { "\(wrappedValue)" }, set: { wrappedValue = Int($0) ?? 0 } ) } } struct TextFieldStepper: View { @Binding var value: Int var body: some View { HStack(spacing: 0) { TextField("", text: $value.toString) .textFieldStyle(.roundedBorder) .frame(width: 50) Stepper("", value: $value) } } }

June 20, 2023 路 1 min 路 Khoa Pham

How to clear TextEditor background in SwiftUI

Issue #920 For iOS 16 and macOS 13.0 TextEditor(text: $text) .scrollContentBackground(.hidden) For below, use [SwiftUI-Introspect](https://github.com/siteline/SwiftUI-Introspect) TextEditor(text: $text) .instrospectTextView { $0.drawsBackground = false }

June 19, 2023 路 1 min 路 Khoa Pham

Learning Metal for SwiftUI

Issue #919 WWDC23 introduces lots of new additions to SwiftUI, notably Metal shader support with these modifiers colorEffect: Returns a new view that applies shader to self as a filter effect on the color of each pixel. layerEffect: Returns a new view that applies shader to self as a filter on the raster layer created from self. distortionEffect: Returns a new view that applies shader to self as a geometric distortion effect on the location of each pixel....

June 16, 2023 路 3 min 路 Khoa Pham

How to use SwiftUI Charts

Issue #907 Read more https://swdevnotes.com/swift/2022/customise-a-line-chart-with-swiftui-charts-in-ios-16/ https://github.com/jordibruin/Swift-Charts-Examples

October 30, 2022 路 1 min 路 Khoa Pham

How to make SwiftUI widget in iOS

Issue #906 Read more https://developer.apple.com/documentation/widgetkit/making-a-configurable-widget What is the purpose of getSnapshot method from WidgetKit

October 30, 2022 路 1 min 路 Khoa Pham

How to show view below title bar for macOS in SwiftUi

Issue #899 Use NSTitlebarAccessoryViewController var titleBarAccessoryVC: NSTitlebarAccessoryViewController { let vc = NSTitlebarAccessoryViewController() let view = HStack { Spacer() Button { } label: { Text("Save") } .buttonStyle(.borderedProminent) .controlSize(.large) } .padding(.horizontal) vc.view = NSHostingView(rootView: view) return vc } let window: NSWindow = ... window.addTitlebarAccessoryViewController(titleBarAccessoryVC)

July 30, 2022 路 1 min 路 Khoa Pham

How to drag using DragGesture in SwiftUI

Issue #898 Change element position using either offset or position, and use DragGesture Use GestureState to store the updating startDragLocation to keep the start location when drag begins, so we can add translation struct MoveModifier: ViewModifier { @Binding var position: CGPoint @GestureState private var startLocation: CGPoint? func body(content: Content) -> some View { content .gesture(gesture) } private var gesture: some Gesture { DragGesture() .onChanged { value in var position = startLocation ?...

July 28, 2022 路 1 min 路 Khoa Pham