显示全屏视图覆盖 TabBar
Show full screen view overlaying also TabBar
我正在尝试全屏显示带有加载程序的视图。我也想覆盖 TabBar,但我不知道该怎么做。让我展示一下我的代码。
这是ProgressViewModifier
。
// MARK: - View - Extension
extension View {
/// Show a loader binded to `isShowing` parameter.
/// - Parameters:
/// - isShowing: `Bool` value to indicate if the loader is to be shown or not.
/// - text: An optional text to show below the spinning circle.
/// - color: The color of the spinning circle.
/// - Returns: The loader view.
func progressView(
isShowing: Binding <Bool>,
backgroundColor: Color = .black,
dimBackground: Bool = false,
text : String? = nil,
loaderColor : Color = .white,
scale: Float = 1,
blur: Bool = false) -> some View {
self.modifier(ProgressViewModifier(
isShowing: isShowing,
backgroundColor: backgroundColor,
dimBackground: dimBackground,
text: text,
loaderColor: loaderColor,
scale: scale,
blur: blur)
)
}
}
// MARK: - ProgressViewModifier
struct ProgressViewModifier : ViewModifier {
@Binding var isShowing : Bool
var backgroundColor: Color
var dimBackground: Bool
var text : String?
var loaderColor : Color
var scale: Float
var blur: Bool
func body(content: Content) -> some View {
ZStack { content
if isShowing {
withAnimation {
showProgressView()
}
}
}
}
}
// MARK: - Private methods
extension ProgressViewModifier {
private func showProgressView() -> some View {
ZStack {
Rectangle()
.fill(backgroundColor.opacity(0.7))
.ignoresSafeArea()
.background(.ultraThinMaterial)
VStack (spacing : 20) {
if isShowing {
ProgressView()
.tint(loaderColor)
.scaleEffect(CGFloat(scale))
if text != nil {
Text(text!)
.foregroundColor(.black)
.font(.headline)
}
}
}
.background(.clear)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
这是 RootTabView
,包含 TabBar 的那个。
struct RootTabView: View {
var body: some View {
TabView {
AddEverydayExpense()
.tabItem {
Label("First", systemImage: "1.circle")
}
AddInvestment()
.tabItem {
Label("Second", systemImage: "2.circle")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
RootTabView()
}
}
这是我的看法。
struct AddEverydayExpense: View {
@ObservedObject private var model = AddEverydayExpenseVM()
@State private var description: String = ""
@State private var cost: String = ""
@State private var date: Date = Date()
@State private var essential: Bool = false
@State private var month: Month?
@State private var category: Category?
private var isButtonDisabled: Bool {
return description.isEmpty ||
cost.isEmpty ||
month == nil ||
category == nil
}
var body: some View {
NavigationView {
VStack {
Form {
Section {
TextField("", text: $description, prompt: Text("Descrizione"))
TextField("", text: $cost, prompt: Text("10€"))
.keyboardType(.numbersAndPunctuation)
DatePicker(date.string(withFormat: "EEEE"), selection: $date)
HStack {
CheckboxView(checked: $essential)
Text("È considerata una spesa essenziale?")
}
.onTapGesture {
essential.toggle()
}
}
Section {
Picker(month?.name ?? "Mese di riferimento", selection: $month) {
ForEach(model.months) { month in
Text(month.name).tag(month as? Month)
}
}
Picker(category?.name ?? "Categoria", selection: $category) {
ForEach(model.categories) { category in
Text(category.name).tag(category as? Category)
}
}
}
Section {
Button("Invia".uppercased()) { print("Button") }
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.font(.headline)
.listRowBackground(isButtonDisabled ? Color.gray.opacity(0.5) : Color.blue)
.foregroundColor(Color.white.opacity(isButtonDisabled ? 0.5 : 1))
.disabled(!isButtonDisabled)
}
}
Spacer()
}
.navigationTitle("Aggiungi Spesa")
}
.progressView(isShowing: $model.isFetching, blur: true)
}
}
如您所见,.progressView(isShowing: $model.isFetching, blur: true)
行发挥了神奇作用。问题是加载程序只显示在当前视图中,而不是选项卡上。 .
我怎样才能达到这个结果?
如果您希望进度视图覆盖整个视图(包括标签栏),它必须位于 TabBar
或以上的视图层次结构中。现在,它位于子视图中 TabBar
的下方。
因为状态需要传递给父级(TabBar
的所有者),所以您需要某种可以传递给子级的状态管理。这可能意味着只是将 Binding
传递给 @State
。我选择展示如何通过使用 @EnvironmentObject
向下传递层次结构的 ObservableObject
来实现这一点,这样您就不必显式传递依赖关系。
class ProgressManager : ObservableObject {
@Published var inProgress = false
}
struct ContentView : View {
@StateObject private var progressManager = ProgressManager()
var body: some View {
TabView {
AddEverydayExpense()
.tabItem {
Label("First", systemImage: "1.circle")
}
AddInvestment()
.tabItem {
Label("Second", systemImage: "2.circle")
}
}
.environmentObject(progressManager)
.progressView(isShowing: $progressManager.inProgress) //<-- Note that this is outside of the `TabBar`
}
}
struct AddEverydayExpense : View {
@EnvironmentObject private var progressManager : ProgressManager
var body: some View {
Button("Progress") {
progressManager.inProgress = true
}
}
}
我正在尝试全屏显示带有加载程序的视图。我也想覆盖 TabBar,但我不知道该怎么做。让我展示一下我的代码。
这是ProgressViewModifier
。
// MARK: - View - Extension
extension View {
/// Show a loader binded to `isShowing` parameter.
/// - Parameters:
/// - isShowing: `Bool` value to indicate if the loader is to be shown or not.
/// - text: An optional text to show below the spinning circle.
/// - color: The color of the spinning circle.
/// - Returns: The loader view.
func progressView(
isShowing: Binding <Bool>,
backgroundColor: Color = .black,
dimBackground: Bool = false,
text : String? = nil,
loaderColor : Color = .white,
scale: Float = 1,
blur: Bool = false) -> some View {
self.modifier(ProgressViewModifier(
isShowing: isShowing,
backgroundColor: backgroundColor,
dimBackground: dimBackground,
text: text,
loaderColor: loaderColor,
scale: scale,
blur: blur)
)
}
}
// MARK: - ProgressViewModifier
struct ProgressViewModifier : ViewModifier {
@Binding var isShowing : Bool
var backgroundColor: Color
var dimBackground: Bool
var text : String?
var loaderColor : Color
var scale: Float
var blur: Bool
func body(content: Content) -> some View {
ZStack { content
if isShowing {
withAnimation {
showProgressView()
}
}
}
}
}
// MARK: - Private methods
extension ProgressViewModifier {
private func showProgressView() -> some View {
ZStack {
Rectangle()
.fill(backgroundColor.opacity(0.7))
.ignoresSafeArea()
.background(.ultraThinMaterial)
VStack (spacing : 20) {
if isShowing {
ProgressView()
.tint(loaderColor)
.scaleEffect(CGFloat(scale))
if text != nil {
Text(text!)
.foregroundColor(.black)
.font(.headline)
}
}
}
.background(.clear)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
这是 RootTabView
,包含 TabBar 的那个。
struct RootTabView: View {
var body: some View {
TabView {
AddEverydayExpense()
.tabItem {
Label("First", systemImage: "1.circle")
}
AddInvestment()
.tabItem {
Label("Second", systemImage: "2.circle")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
RootTabView()
}
}
这是我的看法。
struct AddEverydayExpense: View {
@ObservedObject private var model = AddEverydayExpenseVM()
@State private var description: String = ""
@State private var cost: String = ""
@State private var date: Date = Date()
@State private var essential: Bool = false
@State private var month: Month?
@State private var category: Category?
private var isButtonDisabled: Bool {
return description.isEmpty ||
cost.isEmpty ||
month == nil ||
category == nil
}
var body: some View {
NavigationView {
VStack {
Form {
Section {
TextField("", text: $description, prompt: Text("Descrizione"))
TextField("", text: $cost, prompt: Text("10€"))
.keyboardType(.numbersAndPunctuation)
DatePicker(date.string(withFormat: "EEEE"), selection: $date)
HStack {
CheckboxView(checked: $essential)
Text("È considerata una spesa essenziale?")
}
.onTapGesture {
essential.toggle()
}
}
Section {
Picker(month?.name ?? "Mese di riferimento", selection: $month) {
ForEach(model.months) { month in
Text(month.name).tag(month as? Month)
}
}
Picker(category?.name ?? "Categoria", selection: $category) {
ForEach(model.categories) { category in
Text(category.name).tag(category as? Category)
}
}
}
Section {
Button("Invia".uppercased()) { print("Button") }
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.font(.headline)
.listRowBackground(isButtonDisabled ? Color.gray.opacity(0.5) : Color.blue)
.foregroundColor(Color.white.opacity(isButtonDisabled ? 0.5 : 1))
.disabled(!isButtonDisabled)
}
}
Spacer()
}
.navigationTitle("Aggiungi Spesa")
}
.progressView(isShowing: $model.isFetching, blur: true)
}
}
如您所见,.progressView(isShowing: $model.isFetching, blur: true)
行发挥了神奇作用。问题是加载程序只显示在当前视图中,而不是选项卡上。
我怎样才能达到这个结果?
如果您希望进度视图覆盖整个视图(包括标签栏),它必须位于 TabBar
或以上的视图层次结构中。现在,它位于子视图中 TabBar
的下方。
因为状态需要传递给父级(TabBar
的所有者),所以您需要某种可以传递给子级的状态管理。这可能意味着只是将 Binding
传递给 @State
。我选择展示如何通过使用 @EnvironmentObject
向下传递层次结构的 ObservableObject
来实现这一点,这样您就不必显式传递依赖关系。
class ProgressManager : ObservableObject {
@Published var inProgress = false
}
struct ContentView : View {
@StateObject private var progressManager = ProgressManager()
var body: some View {
TabView {
AddEverydayExpense()
.tabItem {
Label("First", systemImage: "1.circle")
}
AddInvestment()
.tabItem {
Label("Second", systemImage: "2.circle")
}
}
.environmentObject(progressManager)
.progressView(isShowing: $progressManager.inProgress) //<-- Note that this is outside of the `TabBar`
}
}
struct AddEverydayExpense : View {
@EnvironmentObject private var progressManager : ProgressManager
var body: some View {
Button("Progress") {
progressManager.inProgress = true
}
}
}