Lately, I’ve been crafting this fantastic app PastePal that I’m thrilled to share with you today.
As someone who uses Mac, iPhone, and iPad a lot. I constantly find the need to organize my pasteboard and have it accessible across devices in a beautiful and convenient way. Plus I have dozen of data like Slack messages, JIRA tickets, token keys, color codes, and links that I want to organize the best way so I can access later. When I heard that my colleagues have the same problem and can’t find the best solution, it itches me to solve this.
🏄♂️…
In SwiftUI there are fixed frame and flexible frame modifiers.
Use this method to specify a fixed size for a view’s width, height, or both. If you only specify one of the dimensions, the resulting view assumes this view’s sizing behavior in the other dimension.
VStack {
Ellipse()
.fill(Color.purple)
.frame(width: 200, height: 100)
Ellipse()
.fill(Color.blue)
.frame(height: 100)
}
Read the documentation carefully
Always specify at least one size characteristic when calling this method. Pass nil or leave out a characteristic to indicate that the frame should adopt this view’s sizing behavior, constrained by the other non-nil arguments.
The size proposed…
Use NSTextField
with maximumNumberOfLines
import AppKit
import SwiftUIstruct AttributedText: NSViewRepresentable { let attributedString: NSAttributedString init(_ attributedString: NSAttributedString) {
self.attributedString = attributedString
} func makeNSView(context: Context) -> NSTextField {
let textField = NSTextField() textField.lineBreakMode = .byClipping
textField.maximumNumberOfLines = 0
textField.isBordered = false return textField
} func updateNSView(_ nsView: NSTextField, context: Context) {
nsView.attributedStringValue = attributedString
}
}
TextField has problem with wrapping, we can use TextView
struct AttributedTextView: NSViewRepresentable {
typealias NSViewType = NSScrollView let attributedText: NSAttributedString?
let isSelectable: Bool
var insetSize: CGSize = .zero func makeNSView(context: Context) -> NSViewType {
let scrollView = NSTextView.scrollableTextView() let textView = scrollView.documentView…
NSTextView
has this handy method to make scrollable NSTextView NSTextView.scrollableTextView()
. The solution is to get to the responder outside enclosing NSScrollView
, in my case it is the SwiftUI hosting view
…
Use @ViewBuilder
to build dynamic content for our HUD. For blur effect, here I use NSVisualEffectView
, but we can use .blur
modifier also
struct HUD<Content>: View where Content: View {…
With Xcode 12, we can fire up Instrument to profile our app. Select SwiftUI template
There are many profiles in that template, I find SwiftUI and Time Profile very useful. Here’s the profile I run for my app PastePal
This shows how many instance of View with body invocation are there, both for SwiftUI views and our app views
Taking a look at SwiftUI profile, it shows that ClipboardCell
is taking most of the time, here over 7 seconds
@propertyWrapper struct UserDefault<Value> { let key: String let defaultValue: Value let container: UserDefaults = .standard var wrappedValue: Value { get { return container.object(forKey: key) as? Value ?? defaultValue } set…
Use NSSharingService.sharingServices(forItems:)
with an array of one empty string gives a list of sharing items. There we show image
and title
of each menu item
import SwiftUI import AppKit import…
I have an enum that conforms to CaseIterable
that I want to show in Picker
enum Position: String, Codable, CaseIterable, Identifiable {
var id: String { rawValue }
case left
case right
case bottom
case top
}Picker(selection: $preference.position, label: Text("Position")) {
ForEach(Preference.Position.allCases) { position in
Text(position.rawValue)
}
}
It compiles and runs just fine, but Picker does not show current selection regardless of any Picker style I choose. It does not update Binding
at all.
The fix is to specify id
, it looks redundant because of enum conforms to Identifiable
, but it fixes the problem
Picker(selection: $preference.position, label: Text("Position"))…