I learned that it is possible to render system images like so:
Image(nsImage: NSImage(imageLiteralResourceName: NSImage.addTemplateName))
But there seem to be no corresponding template name for spinner:
Do I have to use custom svg?
Xcode 12
it's just a simple view.
ProgressView()
Currently, it's defaulted to CircularProgressViewStyle but you can manually set the style of it by adding the following modifer:
.progressViewStyle(CircularProgressViewStyle())
Also, the style could be anything that conforms to ProgressViewStyle
Xcode 11
You can use NSProgressIndicator directly in SwiftUI:
Implementation
struct ProgressIndicator: NSViewRepresentable {
typealias TheNSView = NSProgressIndicator
var configuration = { (view: TheNSView) in }
func makeNSView(context: NSViewRepresentableContext<ProgressIndicator>) -> NSProgressIndicator {
TheNSView()
}
func updateNSView(_ nsView: NSProgressIndicator, context: NSViewRepresentableContext<ProgressIndicator>) {
configuration(nsView)
}
}
Sample Usage
var body: some View {
ProgressIndicator {
$0.controlTint = .blueControlTint
}
}
You can replace TheNSView with any other NSView you need to use in SwiftUI.
This icon cannot get as Image.
I have add my source code form my swift ui class:
ActivityIndicator
Related
I would like to create a ViewModifier where the output is conditional on the type of content it is modifying.
The best test of the concept I've managed (using Text and TextField as example View types) is as follows:
struct CustomModifier<T: View>: ViewModifier {
#ViewBuilder func body(content: Content) -> some View {
if content is Text.Type {
content.background(Color.red)
} else {
if content is TextField<T>.Type {
content.background(Color.blue)
}
}
content
}
}
The problem with the above modifier is that you need to explicitly provide the generic term when you use the modifier so seems incorrect (and, to my nose, a code smell) as you then need to define a generic on the parent View and then a generic on its parent, etc, etc..
e.g.
struct ContentView<T: View>: View {
var body: some View {
VStack {
Text("Hello world")
.modifier(CustomModifier<Text>())
TextField("Textfield", text: .constant(""))
.modifier(CustomModifier<TextField<T>>())
}
}
}
I managed to get around this problem (with some guidance from Cristik) using this extension on View:
extension View {
func customModifier() -> some View where Self:View {
modifier(CustomModifier<Self>())
}
}
The modifier was tested using the following using iPadOS Playgrounds:
struct ContentView: View {
var body: some View {
Form {
Text("Hello")
.customModifier()
TextField("Text", text: .constant(""))
.customModifier()
}
}
}
This compiles and runs but the output is not what I expected. The Text and TextField views should have different backgrounds (red and blue respectively) but they are displayed unchanged. Overriding the View type checking in the modifier (hard coding the type check to 'true') results in a background colour change so the modifier is being applied; it's the type check that's failing.
I dragged the code into Xcode to try and get a better idea of why this was happening and got an immediate compiler warning advising that the type check would always fail (the modifier name in the screenshot is different - please disregard):
Xcode compiler errors:
This explains why the code does not perform as intended but I'm unable to determine whether I have made a mistake or if there is (in real terms) no way of checking the concrete type of a View sent to a ViewModifier. As best I can tell, the content parameter sent to a ViewModifier does seem to be type erased (based on the methods accessible in Xcode) but there does seem a way of obtaining type information in this context because certain modifiers (e.g. .focused()) only operate on certain types of View (specifically, interactive text controls) and ignore others. This could of course be a private API that we can't access (yet...?)
Any guidance / explanation?
You're right, there are some code smells in that implementation, starting with the fact that you need to write type checks to accomplish the goal. Whenever you start writing is or as? along with concrete types, you should think about abstracting to a protocol.
In your case, you need an abstraction to give you the background color, so a simple protocol like:
protocol CustomModifiable: View {
var customProp: Color { get }
}
extension Text: CustomModifiable {
var customProp: Color { .red }
}
extension TextField: CustomModifiable {
var customProp: Color { .blue }
}
, should be the way to go, and the modifier should be simplifiable along the lines of:
struct CustomModifier: ViewModifier {
#ViewBuilder func body(content: Content) -> some View {
if let customModifiable = content as? CustomModifiable {
content.background(customModifiable.customProp)
} else {
content
}
}
}
The problem is that this idiomatic approach doesn't work with SwiftUI modifiers, as the content received as an argument to the body() function is some internal type of SwiftUI that wraps the original view. This means that you can't (easily) access the actual view the modifier is applied to.
And this is why the is checks always failed, as the compiler correctly said.
Not all is lost, however, as we can work around this limitation via static properties and generics.
protocol CustomModifiable: View {
static var customProp: Color { get }
}
extension Text: CustomModifiable {
static var customProp: Color { .red }
}
extension TextField: CustomModifiable {
static var customProp: Color { .blue }
}
struct CustomModifier<T: CustomModifiable>: ViewModifier {
#ViewBuilder func body(content: Content) -> some View {
content.background(T.customProp)
}
}
extension View {
func customModifier() -> some View where Self: CustomModifiable {
modifier(CustomModifier<Self>())
}
}
The above implementation comes with a compile time benefit, as only Text and TextField are allowed to be modified with the custom modifier. If the developer tries to apply the modifier on a non-accepted type of view, they-ll get a nice Instance method 'customModifier()' requires that 'MyView' conform to 'CustomModifiable', which IMO is better than deceiving about the behaviour of the modifier (i.e. does nothing of some views).
And if you need to support more views in the future, simply add extensions that conform to your protocol.
I have already used Hosting View Controller with connecting my swiftui class to use SwiftUI in storyboard (UIkit) but the issue is I don't want to use it in another page I want to add SwiftUI variable (graphic) to the same page as I created with UIkit. How can I put some stuff in UIkit via SwiftUI, is there something like Container hosting View? there is Container View which helps you to add views in View but you can't edit that like editing Hosting View Controller. Please give me a hand.
I showed where I want the SwiftUI in image below 👇
if you want to use UIKit in SwiftUI, you make use of UIViewControllerRepresentable (A view that represents a UIKit view controller).
if you add UiViewControllerRepresentable Protocol.
you can UIViewController in SwiftUI.
struct ViewControllerRepresentation: UIViewControllerRepresentable {
}
this is Example
final class RedLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
textColor = .red
}
}
struct RepresentableRedLabel: UIViewRepresentable {
var text: String
let redLabel = RedLabel()
func makeUIView(context: Context) -> UILabel {
redLabel
}
func updateUIView(_ uiView: UILabel, context: Context) {
redLabel.text = text
}
}
I'm trying to display a custom SwiftUI view similar to a Toast in Android.
My issue is that I would like to display this particular view above everything else, using the current UIWindow.
Currently, while working on static func displayToastAboveAll() located in my ToastView, this is how far i got
public struct ToastView: View {
static func displayToastAboveAll() {
let window = UIApplication.shared.windows.filter { $0.isKeyWindow }.first // window
let viewToShow = ToastView(my params) // my view to display
// This part I'm not sure of
let hostingController = UIHostingController(rootView: viewToShow)
window?.addSubview(hostingController.view)
}
public var body: some View {
// MyDesign
}
}
Any idea how should I use the window to put the ToastView at its proper place, and still being able to navigate within the app (and use the outlets) while having the view displayed ?
I managed to do what I wanted.
Basically, this code is working, but I had to remove some constraints from my SwiftUI view and add them with UIKit using the static func.
Also, I had to pass by a modifier (see below) and put ToastView init in private.
public struct ToastModifier: ViewModifier {
public func body(content: Content) -> some View {
content
}
}
extension View {
public func toast() -> some View {
ToastView.displayToastAboveAll()
return modifier(ToastModifier())
}
}
This is done to force the use of either modifier (SwiftUI, by doing .toast, just like you'd do .alert) or directly by calling the static func ToastView.displayToastAboveAll() (UIKit).
Indeed, I dont wont this Toast to be a part of the view, I want to trigger it like an alert.
Finally, special warning because passing ToastView into UIHostingViewController will mess with some of the animations.
I had to rewrite animations in UIKit in order to have a nice swipe & fade animation.
I have created my own custom classes for UIKit objects. now i want to use same classes in SwiftUI, How can i achieve that and how much effort it will take.
Also if i want i will need to write same classes in swift UI.
example, I have custom UILable subclass WMLabel.
class WMLabel: UILabel {
var myproperty: String = "" {
didSet {
self.text = myproperty
}
}
}
so how can i use WMLabel in swiftUI?
I have tried ObserverableObject and UIViewRepresentable, but not able to access the properties.
You can definitely use your UIKit classes. To get basic access to the properties, you'll want to be looking at makeUIView, which occurs when the view is first created and updateUIVew.
Using your example code:
class WMLabel: UILabel {
var myproperty: String = "" {
didSet {
self.text = myproperty
}
}
}
struct WMLabelRepresented: UIViewRepresentable {
var text : String
func makeUIView(context: Context) -> WMLabel {
return WMLabel()
}
func updateUIView(_ uiView: WMLabel, context: Context) {
uiView.myproperty = text
}
}
struct ContentView : View {
var body: some View {
WMLabelRepresented(text: "My text")
}
}
If there are things that can't be expressed declaratively, you'll want to look into coordinators and as you mentioned, possible an ObservableObject to communicate data imperatively to your view, but often you can find ways to express most things declaratively.
If you want an example of more complex imperative communication, here's a couple of links to another answers of mine:
https://stackoverflow.com/a/65926143/560942
https://stackoverflow.com/a/66845387/560942
Converting all of your custom classes and interfacing with them is going to be one heck of a chore if you have a few of them. You would end up using something called UIViewRepresentable which requires quite a few things, the most annoying of which called a coordinator. You'd almost be better off rewriting your classes into a SwiftUI version. Here's Apple's documentation on interfacing SwiftUI with UIKit: https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit
Here's an example of conversion of that UILabel into SwiftUI, with accessible properties.
Example Conversion
struct WMLabel: View {
var myProperty: String
var body: some View {
Text(myProperty)
}
}
Example Usage
struct Example: View {
var body: some View {
WMLabel(myProperty: "Hello World!")
}
}
As you can see, there is little code involved in converting something to SwiftUI, if you start getting involved in UIViewRepresentable you have to start playing with coordinators and a bunch of other interfacing methods just to make it work. Sometimes it's required, but in most cases I'd try and avoid it.
Using the SegmentedPickerStyle style Picker could make the control looks like UISegmentedControl. But I wonder how to adjust the segment width in the picker. For examle, the picker in the image has a different width for text.
Is there a way to make the segments the same width in the SwiftUI?
Picker(selection: $store.utility.saliencyType, label: EmptyView()) {
ForEach(Store.Utility.SaliencyType.allCases, id: \.self) { saliencyType in
Text(saliencyType.text)
.tag(saliencyType)
}
}.pickerStyle(SegmentedPickerStyle())
...For examle, the picker in the image has a different width for text.
In case you arrive here seeking for iOS SwiftUI SegmentedPickerStyle solution... I've found the iOS SwiftUI .pickerStyle(SegmentedPickerStyle()) will conform to global UISegmentedControl.appearance() settings, so I've used the following to successfully apportion the width of each segment:
UISegmentedControl.appearance().apportionsSegmentWidthsByContent = true
This is particularly useful if, for example, you want to support Dynamic Type fonts in your app, which can otherwise cause segments with longer names to blow out and get truncated. [aside: I also use this trick to change the SwiftUI segmented picker's font size! see https://stackoverflow.com/a/71834578/3936065]
This is default macOS NSSegmetedControl behavirour
#property NSSegmentDistribution segmentDistribution API_AVAILABLE(macos(10.13));
// Defaults to NSSegmentDistributionFill on 10.13, older systems will continue to behave similarly to NSSegmentDistributionFit
Update: here is workaround, based on finding NSSegmentedControl in run-time view hierarchy.
Disclaimer: Actually it is safe, ie. no crash in run-time, but can stop working in future returning to default behaviour.
So, the idea is to inject NSView via representable into view hierarchy above (!!) Picker, as
Picker(selection: $store.utility.saliencyType, label: EmptyView()) {
ForEach(Store.Utility.SaliencyType.allCases, id: \.self) { saliencyType in
Text(saliencyType.text)
.tag(saliencyType)
}
}
.overlay(NSPickerConfigurator { // << here !!
$0.segmentDistribution = .fillEqually // change style !!
})
.pickerStyle(SegmentedPickerStyle())
and configurator itself
struct NSPickerConfigurator: NSViewRepresentable {
var configure: (NSSegmentedControl) -> Void
func makeNSView(context: Context) -> NSView {
let view = NSView()
DispatchQueue.main.async {
if let holder = view.superview?.superview {
let subviews = holder.subviews
if let nsSegmented = subviews.first?.subviews.first as? NSSegmentedControl {
self.configure(nsSegmented)
}
}
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) {
}
}
Ah the reach down to AppKit method.
Very clever indeed.
However this is not working for me, Monteray 12.3
Went to debug further using Xcode's Visual Debugger and I can see the NSPickerConfigurator class in the view hierarchy but no NSSegmetedControl.
It appears as if apple is clearing up NSViews from the hierarchy.
Time to think pure swiftui.