SwiftUI: SF symbol in tab item is automatically filled [duplicate] - swiftui

I select the systemImage "map" and "person" for the tabItem, but the images are in filled format which must be in hollow format. What's the reason?
struct TestView: View {
var body: some View {
TabView {
Text("Map!")
.tabItem {
Label("Map", systemImage: "map")
}
Text("Profile")
.tabItem {
Label("Person", systemImage: "person")
}
}
}
}
Xcode: 13.1
SF Symbols: 3.1

This is standard SwiftUI behaviour in iOS 15, as it implements by default the recommendations from Apple’s Human Interface Guidelines, which says tab bars should use filled variants of SF Symbols, while sidebars on iPad should use the outline variant.
The effect is achieved by iOS automatically applying the .symbolVariants environment value, as noted in the symbol variants documentation:
SwiftUI sets a variant for you in some environments. For example, SwiftUI automatically applies the fill symbol variant for items that appear in the content closure of the swipeActions(edge:allowsFullSwipe:content:) method, or as the tab bar items of a TabView.
If you absolutely want to get rid of the fill mode, it’s deliberately made tricky but not impossible. You have to override the supplied \.symbolVariants environment variable directly on the Label element, inside your tabItem declaration:
Text("Map!")
.tabItem {
Label("Map", systemImage: "map")
.environment(\.symbolVariants, .none)
}
Using the .symbolVariants(.none) modifier, or trying to set the environment value higher up the view graph, won’t work.
Now that you see how to override the effect, I would still advise using the filled forms in the tab bar. Given that the tab bar no longer has a background colour difference from the rest of the page in many cases, the extra visual weight given to tab items by use of the filled variant lends the right amount of visual weight to those elements.

Related

SwiftUI Is there a way to override the default back button in the navigation view toolbar without needing to add a modifier to every secondary view?

I know that on the view you can add the following modifiers to modify the navigation bar to create a custom back button.
SomeView{ ... }
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
mode.wrappedValue.dismiss()
} label: {
Image(systemName: "chevron.backward")
}
}
However, I do not want to go through my entire app to every secondary view and add these modifiers. I've considered creating a reusable header component, but at the moment, I'm just wondering if there was a way to override the system default for the back button to impact the entire app.
Unfortunately In SwiftUI there is not. You could however, override UINavigationController but it is not recommended as APIs can change.
Look at List for example, we used to set the UITableView appearance background color to .clear to customize List's background, but in iOS 16 this solution works no more.
Extended View and create a func where you put your code in it, then use that function wherever you need!

Why SwiftUI tabItem systemImage is filled?

I select the systemImage "map" and "person" for the tabItem, but the images are in filled format which must be in hollow format. What's the reason?
struct TestView: View {
var body: some View {
TabView {
Text("Map!")
.tabItem {
Label("Map", systemImage: "map")
}
Text("Profile")
.tabItem {
Label("Person", systemImage: "person")
}
}
}
}
Xcode: 13.1
SF Symbols: 3.1
This is standard SwiftUI behaviour in iOS 15, as it implements by default the recommendations from Apple’s Human Interface Guidelines, which says tab bars should use filled variants of SF Symbols, while sidebars on iPad should use the outline variant.
The effect is achieved by iOS automatically applying the .symbolVariants environment value, as noted in the symbol variants documentation:
SwiftUI sets a variant for you in some environments. For example, SwiftUI automatically applies the fill symbol variant for items that appear in the content closure of the swipeActions(edge:allowsFullSwipe:content:) method, or as the tab bar items of a TabView.
If you absolutely want to get rid of the fill mode, it’s deliberately made tricky but not impossible. You have to override the supplied \.symbolVariants environment variable directly on the Label element, inside your tabItem declaration:
Text("Map!")
.tabItem {
Label("Map", systemImage: "map")
.environment(\.symbolVariants, .none)
}
Using the .symbolVariants(.none) modifier, or trying to set the environment value higher up the view graph, won’t work.
Now that you see how to override the effect, I would still advise using the filled forms in the tab bar. Given that the tab bar no longer has a background colour difference from the rest of the page in many cases, the extra visual weight given to tab items by use of the filled variant lends the right amount of visual weight to those elements.

SwiftUI SecureField: How to achieve the same character obscuring behaviour as in UIKit?

My problem is SecureField in SwiftUI doesn’t display characters input by the user for any time at all, it just directly shows the '•' symbol for each character as it's typed - whereas in UIKit, UITextField (with isSecureTextEntry = true) shows the latest character for a second before hiding it behind '•'.
UX testers at my company have requested I bring back the "old behaviour" - but this behaviour doesn't seem part of any public API.
Interestingly this goes for UITextField custom classes injected into SwiftUI using UIViewRepresentable too - they behave in the "SwiftUI way" described above. So there's some contextual behaviour modification going on in SwiftUI for all secure UITextField behaviour? I'd have to completely rewrite my SwiftUI form into a full UIViewController to get back the behaviour (modally pushed UIViewControllers with secure UITextFields do exhibit the desired behaviour.)
Is this a sort of sideline bug in SwiftUI? I see the same thing for SwiftUI in both iOS13 and 14. Anyone seen a workaround or solution?
-EDIT-
After #Asperi's great explanation below, I noticed that my UITextField custom classes injected into SwiftUI using UIViewRepresentable were forcing this behaviour by unnecessarily setting the text binding in the updateUIView call. Using a Coordinator only to deal with text logic fixed the problem for me when using this method.
The observed effect is due to immediate apply to bound string state and immediate react/rebuild of view.
To bring desired behavior beck we need to postpone somehow state update and thus give a chance for SecuredField/UITextField to update self without synchronisation with state.
Here is a demo of possible direction (it is not ideal, but a way to go). Tested with Xcode 12.1 / iOS 14.1.
struct DemoSecureFieldView: View {
#State private var password = "demo"
var textBinding: Binding<String> {
Binding(get: { password },
set: { value in
// more logic can be added to delay _only_ if new symbol added,
// and force apply if next symbol came fast
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
password = value
}
}
)
}
var body: some View {
VStack {
SecureField("Placeholder", text: textBinding)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}.background(Color.pink)
}
}

Hide statusbar in swift ui, when .statusbar(hidden: true) does not work or react

I have an app that loads a tabbar with a view as initial screen, RecipeList(). Inside of RecipeList I call another view to show a recipe full screen. In RecipeList file I have code to show or hide the status bar checking if the recipe detail fullscreen view is loaded or not. It works perfectly if I preview it in xcode, BUT when I preview the code below, which is my Home() view file, and what I want to load as initial screen due to tabbar need, THEN the code inside of RecipeList to show or hide statusbar doesnt work anymore, and status bar is always on.
If i try to hide the statusbar in the code below, it works, but then is always off, something i dont want. Only wanna hide it for the fullscreen view.
I actually used this Introspect package from Github to hide the tabbar when the child view is loaded full screen, and i made it work!
SwiftUI hide TabBar in subview
https://github.com/siteline/SwiftUI-Introspect
Actually, I wonder if anyone has used Introspect to hide the statusbar like the tabbar. I tried to use it, but I am a rockie, I only know a bit of SwiftUI, no Swift, no view controller experience, nothing.
But I have a totally functional app with only this issue, and I am super frustrated not to have the skills to know why the tabbar view is forcing a persistent status bar.
Any help, please?
var body: some View {
ZStack {
Color("background2")
.edgesIgnoringSafeArea(.all)
TabView {
RecipeList().tabItem {
Image(systemName: "book.fill")
.font(.system(size: 24, weight: .bold))
Text("Galería")
}
PostList(section: sectionData[0]).tabItem {
Image(systemName: "list.bullet")
.font(.system(size: 22, weight: .bold))
Text("Listado")
}
}
.accentColor(Color("accent"))
.introspectTabBarController { tabBarController in
// customize here the UITabBarViewController if you like
self.viewModel.tabBarController = tabBarController
}
}
}
I can suggest you using the #EnvironmentObject wrapper which basically allows you to use an object as global state. You can find fair amount of tutorials explaining how to do that and inject it in your initial view so that this object is accessible in the whole view hierarchy.
Once you have that global state set up, you can hide your status bar conditionally like this:
MyOutterWrapper {
Text("Some text")
}
.statusBar(hidden: myGlobalState.statusBarHidden)
If you are using NavigationView note that hiding the status bar works best if you set it up there (also assuming navigation view is your first view that appears).
Now all you got to do is inside your view set the variable to true when entering full screen and set it back to false when exiting. Hope that helps!
EDIT: Forgot to mention that hiding status bar as of June 26, 2020 only works if it's set on the initial view. You cannot change it later and that's the reason we set up this variable in order to go back and change the value dynamically.

SwiftUI and EmptyViews

I have the following code example:
struct ContentView: View {
#State var showText = true
var body: some View {
VStack {
if self.showText {
Text("Hello")
}
//else {
// EmptyView()
// }
}
}
}
When it runs I get the text showing as normal. However, if I set showText to false it still compiles and runs as normal even though the VStack contains nothing - it just doesn't show anything when run. If I uncomment the else clause the example also runs as expected. If I remove all the contents of the VStack however, the example won't compile as nothing is returned.
So my questions are:
Is SwiftUI silently adding an EmptyView when nothing is in the VStack when showText is false ?
Should I really be adding the else clause and return an EmptyView ?
The question isn't completely academic either as I have a use case where I would prefer the whole view to be more or less 'thrown away' rather than have EmptyViews in the hierarchy, though my understanding of SwiftUI is pretty limited at the moment so it may not matter.
VStack is a function builder so it expects to get some value back from the closure. In the case of the if it invokes the buildEither version of the function builder so that satisfies the condition that its not empty. See the function builder proposal. Any ways you should not worry about the EmptyViews. A SwiftUI.View is just a blue print to build the actual view (as apple calls it a cheap value on the stack). It is not the real view object in memory and on the graphics card, as with a UIView or CALayer. The rendering system is going to translate your EmptyView into a noop. SwiftUI.Views get created and discarded all the time and are designed to be cheap unlike UIViews, and the system diff's the SwiftUI.View tree and only applies the delta to the real views in graphics memory similarl to how Flutter, React, and Android Compose work.