Search code examples
swiftuioverlay

Unwanted black bar which overlays part in my swiftUI app


I am new to Xcode and swiftUI so this might be an easy for you but I appreciate all help. I am making a live scoring app. Under my dates bar (to show the scores from different dates), there should be my live scoring bar, however there is a black frame directly under it, then under that there is my scoring boards. If I try and scroll up on it, or put negative padding, it gets put under the black bar which makes it no longer visible.

Here is my code.

// ScoreView.swift


import SwiftUI

struct ScoreView: View {
    @EnvironmentObject private var vm: HomeViewModel
    @State private var initialScrollDone = false

    var body: some View {
        NavigationView {
            ZStack {
                Color.black.edgesIgnoringSafeArea(.all)

                VStack(spacing: 0) {
                    // Top Bar
                    HStack {
                        Button(action: {
                            // Account/log in action
                        }) {
                            Image("Logo_Icon")
                                .resizable()
                                .scaledToFit()
                                .frame(height: 35)
                        }

                        SearchBarView(searchText: $vm.searchText)

                        Spacer()
                    }
                    .padding(.horizontal)
                    .background(Color(red: 27/255, green: 28/255, blue: 30/255))

                    // Date Selector
                    GeometryReader { geometry in
                        ScrollView(.horizontal, showsIndicators: false) {
                            ScrollViewReader { scrollView in
                                LazyHStack(spacing: 10) {
                                    ForEach(vm.dates.indices, id: \.self) { index in
                                        VStack {
                                            
                                            Text(vm.dates[index] == vm.todayString() ? "Today" : vm.dates[index])
                                                .foregroundColor(vm.dates[index] == vm.selectedDate ? .white : Color(red: 122/255, green: 122/255, blue: 124/255))
                                                .bold()
                                                .padding(8)
                                                .cornerRadius(8)
                                                .onTapGesture {
                                                    withAnimation {
                                                        vm.selectedDate = vm.dates[index]
                                                    }
                                                }

                                            Rectangle()
                                                .frame(height: 16)
                                                .foregroundColor(vm.dates[index] == vm.selectedDate ? .blue : .clear)
                                                .cornerRadius(1.8)
                                        }
                                        .id(index)
                                        .frame(width: geometry.size.width / 5.4)
                                    }
                                }
                                .padding(.top, 4.0)
                                .background(Color(red: 27/255, green: 28/255, blue: 30/255))
                                .frame(maxHeight: 37)
                                .onReceive(vm.$selectedDate) { selectedDate in
                                    if let selectedIndex = vm.dates.firstIndex(of: selectedDate) {
                                        withAnimation {
                                            scrollView.scrollTo(selectedIndex, anchor: .center)
                                        }
                                    }
                                    
                                }
                                .onAppear {
                                    if !initialScrollDone, let todayIndex = vm.dates.firstIndex(of: vm.todayString()) {
                                        vm.selectedDate = vm.todayString()
                                        scrollView.scrollTo(todayIndex)
                                        initialScrollDone = true
                                    }
                                }
                            }
                        }
                    }

                    // Live Scores List
                    ScrollView {
                        VStack {
                            Section(header: Text("Favourites").foregroundColor(.white)) {
                                ForEach(vm.favouriteScores, id: \.self) { score in
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                }
                            }

                            Section(header: Text("OJCS").foregroundColor(.white)) {
                                ForEach(vm.ojcsScores, id: \.self) { score in
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                }
                            }

                            Section(header: Text("Andersons Weeknights").foregroundColor(.white)) {
                                ForEach(vm.andersonsWeeknightsScores, id: \.self) { score in
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                    ScoreRowView(score: score)
                                }
                            }
                        }
                        .background(Color.black)
                    }
                }
                .edgesIgnoringSafeArea(.bottom) // Ensure that ScrollView extends below the date selector
            }
        }
        .navigationBarHidden(true) // Hide the navigation bar
    }
}

struct ScoreRowView: View {
    var score: LiveScoreModel

    var body: some View {
        HStack {
            VStack {
                Image("Logo_Icon")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 20, height: 20)
                    .padding(.horizontal)

                Image("Logo_Icon")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 20, height: 20)
                    .padding(.horizontal)
            }

            VStack(alignment: .leading) {
                Text(score.teamName)
                Text(score.teamName)
            }

            Spacer()

            VStack(alignment: .trailing) {
                Text("Score: \(Int.random(in: 0...10)) - \(Int.random(in: 0...10))")
                Text("Time: \(score.gameTime)")
            }
        }
        .listRowBackground(Color.clear)
        .padding(.vertical, 8)
        .background(Color(red: 36/255, green: 37/255, blue: 39/255))
        .foregroundColor(.white)
        .cornerRadius(8)
    }
}

struct ScoreView_Previews: PreviewProvider {
    static var previews: some View {
        ScoreView()
            .environmentObject(HomeViewModel())
    }
}

If you would like to run this program, create a file called HomeViewModel and paste this code into it:

import Foundation
import Combine

class HomeViewModel: ObservableObject {
    @Published var searchText: String = ""
    @Published var favouriteScores: [LiveScoreModel] = []
    @Published var ojcsScores: [LiveScoreModel] = []
    @Published var andersonsWeeknightsScores: [LiveScoreModel] = []
    @Published var dates: [String] = []
    @Published var selectedDate: String = ""
    
    init() {
        // Sample data
        let sampleScores: [LiveScoreModel] = [
            LiveScoreModel(teamLogo: "team1_logo", teamName: "Team 1", category: "Favourites", score: 10, gameTime: "2:30 PM"),
            LiveScoreModel(teamLogo: "team2_logo", teamName: "Team 2", category: "OJCS", score: 8, gameTime: "3:45 PM"),
            LiveScoreModel(teamLogo: "team3_logo", teamName: "Team 3", category: "Andersons Weeknights", score: 12, gameTime: "5:15 PM"),
            // Add more
        ]
        
        self.favouriteScores = sampleScores.filter { $0.category == "Favourites" }
        self.ojcsScores = sampleScores.filter { $0.category == "OJCS" }
        self.andersonsWeeknightsScores = sampleScores.filter { $0.category == "Andersons Weeknights" }
        
        let currentDate = Date()
        self.dates = generateDates(from: currentDate, daysBefore: 7, daysAfter: 7)
    }
    
    func todayString() -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "MMM dd"
        return dateFormatter.string(from: Date())
    }
    
    private func generateDates(from startDate: Date, daysBefore: Int, daysAfter: Int) -> [String] {
        var dates: [String] = []
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM dd"
        
        for day in -daysBefore...daysAfter {
            if let date = Calendar.current.date(byAdding: .day, value: day, to: startDate) {
                let dateString = formatter.string(from: date)
                dates.append(dateString)
            }
        }
        
        return dates
    }
}

Here are some images to help.

1 2

I've figured out that the bug is in the GeometryReader part of my date selector. That is the part that is causing the error.

I've spent the past couple days staring at this code trying to figure it out but I can't. I've tried removing the ZStack, but that just messes up everything and keeps the black bar there, I've tried adding paddings in different places and tried manually setting .frame(height: ), but it doesn't change anything.

I appreciate any help. Thank you for your time.


Solution

  • The issue is due to the GeometryReader where you render the Date selector is occupying all that black space you see. You can either limit it or you use an overlay like I did:

    // Date Selector
    GeometryReader { geometry in
      ScrollView(.horizontal, showsIndicators: false) {
        ScrollViewReader { scrollView in
          LazyHStack(spacing: 10) {
            ForEach(vm.dates.indices, id: \.self) { index in
              VStack {
    
                Text(vm.dates[index] == vm.todayString() ? "Today" : vm.dates[index])
                  .foregroundColor(
                    vm.dates[index] == vm.selectedDate
                      ? .white : Color(red: 122 / 255, green: 122 / 255, blue: 124 / 255)
                  )
                  .bold()
                  .padding(8)
                  .cornerRadius(8)
                  .onTapGesture {
                    withAnimation {
                      vm.selectedDate = vm.dates[index]
                    }
                  }
    
                Rectangle()
                  .frame(height: 16)
                  .foregroundColor(vm.dates[index] == vm.selectedDate ? .blue : .clear)
                  .cornerRadius(1.8)
              }
              .id(index)
              .frame(width: geometry.size.width / 5.4)
            }
          }
          .padding(.top, 4.0)
          .background(Color(red: 27 / 255, green: 28 / 255, blue: 30 / 255))
          .frame(maxHeight: 37)
          .onReceive(vm.$selectedDate) { selectedDate in
            if let selectedIndex = vm.dates.firstIndex(of: selectedDate) {
              withAnimation {
                scrollView.scrollTo(selectedIndex, anchor: .center)
              }
            }
    
          }
          .onAppear {
            if !initialScrollDone, let todayIndex = vm.dates.firstIndex(of: vm.todayString()) {
              vm.selectedDate = vm.todayString()
              scrollView.scrollTo(todayIndex)
              initialScrollDone = true
            }
          }
        }
      }
    }  //: GEOMETRY
    .overlay(alignment: .center) {
      VStack {
        Text("Place Score bar here")
          .foregroundStyle(.white)
      }
    }
    

    Let me know if this can be a good solution for you!