Removing top and bottom lines on a list on iPad - ios

I have an app for iPad that uses a navigation list, and another list to display items. The items list always displays a top and bottom lines, even when no items are there.
I have tried .listStyle(.plain) and .listRowSeparator(.hidden) but nothing helps.
Anyone has any idea how to remove those lines? I can't find anything on Apple's documentation or here at SO.
Thanks.
Code:
struct ContentView: View {
#State var selection: Set<Int> = [0]
var body: some View {
NavigationView {
List(selection: self.$selection) {
NavigationLink(destination: ItemsList(wantStarred: 0)) {
Label("All Items", systemImage: "chart.bar.doc.horizontal").tag(0)
}
NavigationLink(destination: ItemsList(wantStarred: 1)) {
Label("Favorites", systemImage: "star.fill")
}
}
.listStyle(SidebarListStyle())
.frame(minWidth: 150, idealWidth: 150, maxHeight: .infinity)
ItemsList(wantStarred: 0)
}
}
}
struct ItemsList: View {
#State var wantStarred: Int
var body: some View {
List {
//...
}
.listStyle(.plain)
.listRowSeparator(.hidden)
.overlay {
if items.count == 0 {
Text("Create a new item.").fontWeight(.light)
}
}
}
}

The listRowSeparator modifier should be inside list
List {
Text("")
.listRowSeparator(.hidden) // << like here !!
}
.listStyle(.plain)
Tested with Xcode 13.4 / iOS 15.5

Related

ScrollViewReader not scrolling to the correct id with anchor

Problem
First time when the button "Go to 990" is tapped the scroll view doesn't scroll to 990 on top. (see screenshot below)
The anchor is not respected.
Second time it scrolls to 990 top correctly.
Questions
What is wrong and how can it be fixed?
Versions
Xcode 14
Code
struct ContentView: View {
#State private var scrollProxy: ScrollViewProxy?
var body: some View {
NavigationStack {
ScrollViewReader { proxy in
List(0..<1000, id: \.self) { index in
VStack(alignment: .leading) {
Text("cell \(index)")
.font(.title)
Text("text 1")
.font(.subheadline)
Text("text 2")
.font(.subheadline)
}
}
.onAppear {
scrollProxy = proxy
}
}
.toolbar {
ToolbarItem {
Button("Go to 990") {
scrollProxy?.scrollTo(990, anchor: .top)
}
}
}
}
#if os(macOS)
.frame(minWidth: 500, minHeight: 300)
#endif
}
}
Screenshot

SwiftUI LazyHStack TabView inside ScrollView is hidden

I've got the following view:
#if DEBUG
struct MyTestView_Previews: PreviewProvider {
static var previews: some View {
MyTestView()
}
}
#endif
struct MyTestView: View {
#State var selectedTab: Int = 0
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
ScrollView {
LazyHStack {
TabView(selection: $selectedTab) {
ForEach(0...3, id: \.self) { i in
Text(String(i))
}
}
.animation(.easeInOut)
.frame(width: UIScreen.main.bounds.width)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
Spacer().frame(height: geometry.safeAreaInsets.bottom)
}
.navigationBarTitle("Test")
.edgesIgnoringSafeArea(.bottom)
}
}
}
This used to work fine pre XCode 13 & iOS 15. Now with the latest versions, my TabView items are no longer shown. At all. If I remove ScrollView which gives me the neccesary vertical scrolling in my real scenario, I can see my items again.
How can I have vertical scrolling in LazyHStack -> TabView scenarios?
After a day, I figured I can just move my ScrollView down:
struct MyTestView: View {
#State var selectedTab: Int = 0
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
LazyHStack {
TabView(selection: $selectedTab) {
ForEach(0...3, id: \.self) { i in
ScrollView {
Text(String(i))
}
}
}
.animation(.easeInOut)
.frame(width: UIScreen.main.bounds.width)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
Spacer().frame(height: geometry.safeAreaInsets.bottom)
}
.navigationBarTitle("Test")
.edgesIgnoringSafeArea(.bottom)
}
}
}
Now everything works.

Navigation View -Notes Like Sidebar - Toggle button

I am very new to iOS development and Swift UI. I am making an app for our company. I believe Apple Notes like approach is best. I got most working tanks to some Udemmy courses and a couple of weeks of intense Googling. But I can't figure out how to implement the toggle sidebar button. I am probably searching for something obvious but using the wrong terminology.
I am talking about this:
When I remove most of the code, I have a structure like this:
NavigationView {
List {
Section(header: RoomHeader()) {
ForEach(sections) { section in
NavigationLink(destination: ViewRoom(section: section)) {
RoomListItem(section: section)
}
}
}
}
.navigationTitle("Rooms")
.listStyle(InsetGroupedListStyle())
}
The ViewRoom class
import SwiftUI
struct ViewRoom: View {
var room: RoomModel
var body: some View {
ZStack {
ScrollView {
VStack {
controls
title
// ....
}
.padding()
}
bottomBar
}
.navigationTitle(room.name)
.navigationBarItems(
trailing: HStack {
// ...
}
)
}
var controls: some View {
HStack {
Spacer()
// Couldn't find the icon on SF Symbols but this is the toggle button
Button(action: {}, label: {
Image(systemName: "rectangle.portrait.arrowtriangle.2.outward")
})
}
.font(.system(size: 24))
.padding(.top, 15)
}
// ...
}
I'd appreciate it if you could let me know if how to implement this toggle feature.
You can use Zstack & animation & transition features of SwiftUI. I made a sample for you to dig more into it and explore more about above mentioned concepts.
struct ContentView: View {
#State private var showDetails = false
var body: some View {
ZStack {
if showDetails {
LeftView()
.transition(.move(edge: .leading))
.zIndex(1)
} else {
Button("Press to show details") {
withAnimation(.spring()) {
showDetails.toggle()
}
}
}
}
.onTapGesture {
withAnimation(.spring()) {
showDetails.toggle()
}
}
}
}
Below is leftView which will animated from left
struct LeftView: View {
var body: some View {
GeometryReader { geometry in
VStack {
Text("Hello, World!")
}
.frame(width: 200, height: geometry.size.height)
.background(Color.blue)
}
}
}

How to stop SwiftUI change the fixed padding when we switch button content between Text and Image?

So I have two buttons on the bottom of my screen, button A and button B, somewhere along the line I need to replace the image in the button with text, so we do so by changing the Bool in the top.
Although we apply the same modifiers, the padding of button B changes, and the UI moves around, it seems as if the text claims more space. Desired situation: button A and B, should not move around when changing the button Image to Text.
import SwiftUI
private var showImage: Bool = true
struct SwiftUIView: View {
var body: some View {
VStack {
Spacer()
Button(action: {
print("CLICK")
}) {
Image(systemName: "a.circle")
.modifier(TestButtonModifier())
}
.padding(10)
Button(action: {
print("CLICK")
}) {
if showImage {
Image(systemName: "b.circle")
.modifier(TestButtonModifier())
} else {
Text("B")
.modifier(TestButtonModifier())
}
}
.padding(10)
} //: VSTACK
}
}
struct TestButtonModifier: ViewModifier {
func body(content: Content) -> some View {
content
.font(.system(size: 52, weight: .regular))
.frame(minWidth: 0, maxWidth: .infinity, maxHeight: 100)
.background(Color.black)
.padding(2)
.foregroundColor(Color.white)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
It is due to VStack spacing, which by default differs between different pairs of subviews, so specify some explicit (or remove at all, ie set to zero)
struct SwiftUIView: View {
var body: some View {
VStack(spacing: 0) { // << here !!
// .. other code

SwiftUI Hide TabView bar inside NavigationLink views

I have a TabView and separate NavigationView stacks for every Tab item. It works well but when I open any NavigationLink the TabView bar is still displayed. I'd like it to disappear whenever I click on any NavigationLink.
struct MainView: View {
#State private var tabSelection = 0
var body: some View {
TabView(selection: $tabSelection) {
FirstView()
.tabItem {
Text("1")
}
.tag(0)
SecondView()
.tabItem {
Text("2")
}
.tag(1)
}
}
}
struct FirstView: View {
var body: some View {
NavigationView {
NavigationLink(destination: FirstChildView()) { // How can I open FirstViewChild with the TabView bar hidden?
Text("Go to...")
}
.navigationBarTitle("FirstTitle", displayMode: .inline)
}
}
}
I found a solution to put a TabView inside a NavigationView, so then after I click on a NavigationLink the TabView bar is hidden. But this messes up NavigationBarTitles for Tab items.
struct MainView: View {
#State private var tabSelection = 0
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
...
}
}
}
}
struct FirstView: View {
var body: some View {
NavigationView {
NavigationLink(destination: FirstChildView()) {
Text("Go to...")
}
.navigationBarTitle("FirstTitle", displayMode: .inline) // This will not work now
}
}
}
With this solution the only way to have different NavigationTabBars per TabView item, is to use nested NavigationViews. Maybe there is a way to implement nested NavigationViews correctly? (As far as I know there should be only one NavigationView in Navigation hierarchy).
How can I hide TabView bar inside NavigationLink views correctly in SwiftUI?
I really enjoyed the solutions posted above, but I don't like the fact that the TabBar is not hiding according to the view transition.
In practice, when you swipe left to navigate back when using tabBar.isHidden, the result is not acceptable.
I decided to give up the native SwiftUI TabView and code my own.
The result is more beautiful in the UI:
Here is the code used to reach this result:
First, define some views:
struct FirstView: View {
var body: some View {
NavigationView {
VStack {
Text("First View")
.font(.headline)
}
.navigationTitle("First title")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.yellow)
}
}
}
struct SecondView: View {
var body: some View {
VStack {
NavigationLink(destination: ThirdView()) {
Text("Second View, tap to navigate")
.font(.headline)
}
}
.navigationTitle("Second title")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.orange)
}
}
struct ThirdView: View {
var body: some View {
VStack {
Text("Third View with tabBar hidden")
.font(.headline)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.red.edgesIgnoringSafeArea(.bottom))
}
}
Then, create the TabBarView (which will be the root view used in your app):
struct TabBarView: View {
enum Tab: Int {
case first, second
}
#State private var selectedTab = Tab.first
var body: some View {
VStack(spacing: 0) {
ZStack {
if selectedTab == .first {
FirstView()
}
else if selectedTab == .second {
NavigationView {
VStack(spacing: 0) {
SecondView()
tabBarView
}
}
}
}
.animation(nil)
if selectedTab != .second {
tabBarView
}
}
}
var tabBarView: some View {
VStack(spacing: 0) {
Divider()
HStack(spacing: 20) {
tabBarItem(.first, title: "First", icon: "hare", selectedIcon: "hare.fill")
tabBarItem(.second, title: "Second", icon: "tortoise", selectedIcon: "tortoise.fill")
}
.padding(.top, 8)
}
.frame(height: 50)
.background(Color.white.edgesIgnoringSafeArea(.all))
}
func tabBarItem(_ tab: Tab, title: String, icon: String, selectedIcon: String) -> some View {
ZStack(alignment: .topTrailing) {
VStack(spacing: 3) {
VStack {
Image(systemName: (selectedTab == tab ? selectedIcon : icon))
.font(.system(size: 24))
.foregroundColor(selectedTab == tab ? .primary : .black)
}
.frame(width: 55, height: 28)
Text(title)
.font(.system(size: 11))
.foregroundColor(selectedTab == tab ? .primary : .black)
}
}
.frame(width: 65, height: 42)
.onTapGesture {
selectedTab = tab
}
}
}
This solution also allows a lot of customization in the TabBar.
You can add some notifications badges, for example.
If we talk about standard TabView, the possible workaround solution can be based on TabBarAccessor from my answer on Programmatically detect Tab Bar or TabView height in SwiftUI
Here is a required modification in tab item holding NavigationView. Tested with Xcode 11.4 / iOS 13.4
struct FirstTabView: View {
#State private var tabBar: UITabBar! = nil
var body: some View {
NavigationView {
NavigationLink(destination:
FirstChildView()
.onAppear { self.tabBar.isHidden = true } // !!
.onDisappear { self.tabBar.isHidden = false } // !!
) {
Text("Go to...")
}
.navigationBarTitle("FirstTitle", displayMode: .inline)
}
.background(TabBarAccessor { tabbar in // << here !!
self.tabBar = tabbar
})
}
}
Note: or course if FirstTabView should be reusable and can be instantiated standalone, then tabBar property inside should be made optional and handle ansbsent tabBar explicitly.
Thanks to another Asperi's answer I was able to find a solution which does not break animations and looks natural.
struct ContentView: View {
#State private var tabSelection = 1
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
FirstView()
.tabItem {
Text("1")
}
.tag(1)
SecondView()
.tabItem {
Text("2")
}
.tag(2)
}
// global, for all child views
.navigationBarTitle(Text(navigationBarTitle), displayMode: .inline)
.navigationBarHidden(navigationBarHidden)
.navigationBarItems(leading: navigationBarLeadingItems, trailing: navigationBarTrailingItems)
}
}
}
struct FirstView: View {
var body: some View {
NavigationLink(destination: Text("Some detail link")) {
Text("Go to...")
}
}
}
struct SecondView: View {
var body: some View {
Text("We are in the SecondView")
}
}
Compute navigationBarTitle and navigationBarItems dynamically:
private extension ContentView {
var navigationBarTitle: String {
tabSelection == 1 ? "FirstView" : "SecondView"
}
var navigationBarHidden: Bool {
tabSelection == 3
}
#ViewBuilder
var navigationBarLeadingItems: some View {
if tabSelection == 1 {
Text("+")
}
}
#ViewBuilder
var navigationBarTrailingItems: some View {
if tabSelection == 1 {
Text("-")
}
}
}
How about,
struct TabSelectionView: View {
#State private var currentTab: Tab = .Scan
private enum Tab: String {
case Scan, Validate, Settings
}
var body: some View {
TabView(selection: $currentTab){
ScanView()
.tabItem {
Label(Tab.Scan.rawValue, systemImage: "square.and.pencil")
}
.tag(Tab.Scan)
ValidateView()
.tabItem {
Label(Tab.Validate.rawValue, systemImage: "list.dash")
}
.tag(Tab.Validate)
SettingsView()
.tabItem {
Label(Tab.Settings.rawValue, systemImage: "list.dash")
}
.tag(Tab.Settings)
}
.navigationBarTitle(Text(currentTab.rawValue), displayMode: .inline)
}
}
I also faced this problem. I don't want to rewrite, but the solution is in my github. I wrote everything in detail there
https://github.com/BrotskyS/AdvancedNavigationWithTabView
P.S: I have no reputation to write comments. Hikeland's solution is not bad. But you do not save the State of the page. If you have a ScrollView, it will reset to zero every time when you change tab
Also you can create very similar custom navBar for views in TabView
struct CustomNavBarView<Content>: View where Content: View {
var title: String = ""
let content: Content
init(title: String, #ViewBuilder content: () -> Content) {
self.title = title
self.content = content()
}
var body: some View {
content
.safeAreaInset(edge: .top, content: {
HStack{
Spacer()
Text(title)
.fontWeight(.semibold)
Spacer()
}
.padding(.bottom, 10)
.frame(height: 40)
.frame(maxWidth: .infinity)
.background(.ultraThinMaterial)
.overlay {
Divider()
.frame(maxHeight: .infinity, alignment: .bottom)
}
})
}
}
CustomNavBarView(title: "Create ad"){
ZStack{
NavigationLink(destination: SetPinMapView(currentRegion: $vm.region, region: vm.region), isActive: $vm.showFullMap) {
Color.clear
}
Color("Background").ignoresSafeArea()
content
}
}

Resources