Remove separator line from list iOS 14 [duplicate] - swift

I'm trying to remove the "row" separators (known as dividers in SwiftUI) from a List in SwiftUI.
I went through the List documentation, but I haven't been able to find a modifier for that.
Any help would be appreciated.

iOS 15:
This year Apple introduced a new modifier .listRowSeparator that can be used to style the separators. you can pass .hidden to hide it:
List {
ForEach(items, id:\.self) {
Text("Row \($0)")
.listRowSeparator(.hidden)
}
}
iOS 14
Apple introduced LazyVStack In iOS 14. you may consider using it instead of list for this:
ScrollView {
LazyVStack {
ForEach((1...100), id: \.self) {
Text("Placeholder \($0)")
}
}
}
Keep in mind that LazyVStack is lazy and doesn't render all rows all the time. So they are very performant and suggested by Apple itself in WWDC 2020.
iOS 13
There is a UITableView behind SwiftUI's List for iOS. So to remove
Extra separators (below the list):
you need a tableFooterView and to remove
All separators (including the actual ones):
you need separatorStyle to be .none
init() {
// To remove only extra separators below the list:
UITableView.appearance().tableFooterView = UIView()
// To remove all separators including the actual ones:
UITableView.appearance().separatorStyle = .none
}
var body: some View {
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
}

iOS 13 builds only
while this solution works correctly let's clean up the work using
ViewModifier
public struct ListSeparatorStyleNoneModifier: ViewModifier {
public func body(content: Content) -> some View {
content.onAppear {
UITableView.appearance().separatorStyle = .none
}.onDisappear {
UITableView.appearance().separatorStyle = .singleLine
}
}
}
now let's make a small extension that would help to hide the details
extension View {
public func listSeparatorStyleNone() -> some View {
modifier(ListSeparatorStyleNoneModifier())
}
}
As you can see, we’ve wrapped our appearance setting code into a neat little view modifier.
you can declare it directly now
List {
Text("1")
Text("2")
Text("3")
}.listSeparatorStyleNone()

You may use ForEach within a ScrollView instead of List for dynamic views without any styling

iOS 13 builds only:
The current workaround is to remove them via UIAppearance:
UITableView.appearance(whenContainedInInstancesOf:
[UIHostingController<ContentView>.self]
).separatorStyle = .none

iOS 13 builds only:
Adding UITableView.appearance().separatorColor = .clear for initializer
struct SomeView: View {
init() {
UITableView.appearance().separatorColor = .clear
}
}
I hope you to resolve this problem.

iOS 13 builds only:
See existing answers using UITableView.appearance().
⚠️ Be aware that in the iOS 14 SDK, List does not appear to be backed by UITableView. See the alternate solution below:
iOS 14 Xcode 12 Beta 1 only:
I do have a pure SwiftUI solution for iOS 14, but who knows how long it's going to continue working for. It relies on your content being the same size (or larger) than the default list row and having an opaque background.
⚠️ This does not work for iOS 13 builds.
Tested in Xcode 12 beta 1:
yourRowContent
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
.frame(
minWidth: 0, maxWidth: .infinity,
minHeight: 44,
alignment: .leading
)
.listRowInsets(EdgeInsets())
.background(Color.white)
Or if you're looking for a reusable ViewModifier:
import SwiftUI
struct HideRowSeparatorModifier: ViewModifier {
static let defaultListRowHeight: CGFloat = 44
var insets: EdgeInsets
var background: Color
init(insets: EdgeInsets, background: Color) {
self.insets = insets
var alpha: CGFloat = 0
UIColor(background).getWhite(nil, alpha: &alpha)
assert(alpha == 1, "Setting background to a non-opaque color will result in separators remaining visible.")
self.background = background
}
func body(content: Content) -> some View {
content
.padding(insets)
.frame(
minWidth: 0, maxWidth: .infinity,
minHeight: Self.defaultListRowHeight,
alignment: .leading
)
.listRowInsets(EdgeInsets())
.background(background)
}
}
extension EdgeInsets {
static let defaultListRowInsets = Self(top: 0, leading: 16, bottom: 0, trailing: 16)
}
extension View {
func hideRowSeparator(
insets: EdgeInsets = .defaultListRowInsets,
background: Color = .white
) -> some View {
modifier(HideRowSeparatorModifier(
insets: insets,
background: background
))
}
}
struct HideRowSeparator_Previews: PreviewProvider {
static var previews: some View {
List {
ForEach(0..<10) { _ in
Text("Text")
.hideRowSeparator()
}
}
.previewLayout(.sizeThatFits)
}
}

From: Swiftui Views Mastery Book SwiftUI 2.0 Mark Moeykens
.listStyle(SidebarListStyle()) # IOS 14
You can apply this new list style which will remove the separator lines.

For iOS13,iOS14,iOS15,and remove the separator at the top of the first cell
Add viewModifier
extension View {
/// 隐藏 List 中的 分割线
func hideRowSeparator(insets: EdgeInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0),
background: Color = .white) -> some View {
modifier(HideRowSeparatorModifier(insets: insets, background: background))
}
}
struct HideRowSeparatorModifier: ViewModifier {
static let defaultListRowHeight: CGFloat = 44
var insets: EdgeInsets
var background: Color
init(insets: EdgeInsets, background: Color) {
self.insets = insets
var alpha: CGFloat = 0
if #available(iOS 14.0, *) {
UIColor(background).getWhite(nil, alpha: &alpha)
assert(alpha == 1, "Setting background to a non-opaque color will result in separators remaining visible.")
}
self.background = background
}
func body(content: Content) -> some View {
content
.padding(insets)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: Self.defaultListRowHeight)
.listRowInsets(EdgeInsets())
.overlay(
VStack {
HStack {}
.frame(maxWidth: .infinity)
.frame(height: 1)
.background(background)
Spacer()
HStack {}
.frame(maxWidth: .infinity)
.frame(height: 1)
.background(background)
}
.padding(.top, -1)
)
}
}
Usage
struct ContentView: View {
var body: some View {
List {
ForEach(0 ..< 30) { item in
HStack(alignment: .center, spacing: 30) {
Text("Hello, world!:\(item)").padding()
}
.hideRowSeparator(background: .white)
}
}
.listStyle(PlainListStyle())
}
}
You can find here
https://github.com/wangrui460/HiddenListLine4SwiftUI
iOS / SwiftUI 技术交流
我创建了一个 微信 iOS 技术交流群、SwiftUI 技术交流群,欢迎小伙伴们加入一起交流学习~
可以加我微信我拉你进去(备注iOS),我的微信号 wr1204607318

Related

Full size AccessoryWidgetBackground() in accessoryRectangular family

I'd like to add a full size AccessoryWidgetBackground() to an accessoryRectangular widget family.
I made a brand new project and added a brand new widget. Then I changed the view to:
struct Some_WidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
AccessoryWidgetBackground()
.frame(width: .infinity, height: .infinity)
.ignoresSafeArea()
}
}
Here's what I get:
Is there a way to take up the whole view with the background blur?
Here is a possible solution. Tested with Xcode 11.4 / iOS 13.4
struct Some_WidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
ZStack {
Color.clear.edgesIgnoringSafeArea(.all) // << here !!
AccessoryWidgetBackground()
.edgesIgnoringSafeArea(.all)
}
}
}
The answer to the question as posed is: this is not possible in iOS 16.0, the view is cropped as can be seen from the inner box in the image in the question.
The workaround I settled for is to create a rounded background view and add it in a ZStack:
var body: some View {
ZStack {
OptionalBlurView(showBlur: true)
// etc
Here's the background view, it works in previews too.
#available(iOSApplicationExtension 16.0, *)
struct OptionalBlurView: View {
var showBlur: Bool
#Environment(\.widgetFamily) var family
var body: some View {
if showBlur {
blurView
} else {
EmptyView()
}
}
var blurView: some View {
#if targetEnvironment(simulator)
// at the time of coding, AccessoryWidgetBackground does not show in previews, so this is an aproximation
switch family {
case .accessoryCircular:
return Circle()
.opacity(0.3)
.eraseToAnyView()
default:
return Rectangle()
.clipShape(RoundedRectangle(cornerSize: CGSize(width: 10, height: 10), style: .continuous))
.opacity(0.3)
.eraseToAnyView()
}
#else
AccessoryWidgetBackground()
.clipShape(RoundedRectangle(cornerSize: CGSize(width: 10, height: 10), style: .continuous))
.opacity(0.7)
#endif
}
}

How do I make conjoined buttons in SwiftUI? (MacOS)

There are buttons present in apps, such as TextEdit where the sides of adjacent buttons are joined together. Does anyone know how I could replicate this visual? I'm working in SwiftUI on MacOS, and answers for MacOS 10 & 11 would be appreciated.
I don’t think that’s possible in SwiftUI, unless you want to build it from scratch. What you’re seeing in TextEdit is an NSSegmentedControl that allows multiple selection: https://developer.apple.com/design/human-interface-guidelines/macos/selectors/segmented-controls/
In SwiftUI, segmented controls are made using Picker, which doesn’t allow multiple selection. Your best bet is to wrap NSSegmentedControl in an NSHostingView.
Replicating these buttons using SwiftUI is not all that difficult.
Here is a very basic approach, adjust the colors, corners etc... to your liking:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
TextFormats()
}
}
struct GrayButtonStyle: ButtonStyle {
let w: CGFloat
let h: CGFloat
init(w: CGFloat, h: CGFloat) {
self.w = w
self.h = h
}
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.foregroundColor(Color.white)
.frame(width: w, height: h)
.padding(5)
.background(Color(UIColor.systemGray4))
.overlay(Rectangle().strokeBorder(Color.gray, lineWidth: 1))
}
}
struct TextFormats: View {
let sx = CGFloat(20)
let color = Color.black
#State var bold = false
#State var italic = false
#State var underline = false
var body: some View {
HStack (spacing: 0) {
Group {
Button(action: { bold.toggle() }) {
Image(systemName: "bold").resizable().frame(width: sx, height: sx)
.foregroundColor(bold ? .blue : color)
}
Button(action: { italic.toggle() }) {
Image(systemName: "italic").resizable().frame(width: sx, height: sx)
.foregroundColor(italic ? .blue : color)
}
Button(action: { underline.toggle() }) {
Image(systemName: "underline").resizable().frame(width: sx, height: sx)
.foregroundColor(underline ? .blue : color)
}
}.buttonStyle(GrayButtonStyle(w: sx+5, h: sx+5))
}.padding(1)
.overlay(RoundedRectangle(cornerRadius: 5).strokeBorder(.white, lineWidth: 2))
.clipped(antialiased: true)
}
}

How do I make all views the same height in a SwiftUI View with an HStack?

I want a simple graph with a colored rectangle of variable height for each data point.
The white space below the colored rectangle should expand so that the bottom numbers line up, the way the top row of numbers does.
This is my view:
So I would like an idiomatic solution to getting the bottom row of numbers to line up with the 59. Any advice that points me in the right direction is welcome. Thanks.
Here's what I have so far:
struct DemoView: View {
var dataSource = [1, 0, 34, 12, 59, 44]
/// Provide a Dynamic Height Based on the Tallest View in the Row
#State private var height: CGFloat = .zero // < calculable height
/// The main view is a row of variable height views
var body: some View {
HStack(alignment: .top) {
Spacer()
/// i want these to all be the same height
ForEach(0 ..< 6) { index in
VStack {
Text("\(index)")
Rectangle()
.fill(Color.orange)
.frame(width: 20, height: CGFloat(self.dataSource[index]))
Text("\(dataSource[index])")
}
}
Spacer()
}
.alignmentGuide(.top, computeValue: { d in
DispatchQueue.main.async {
self.height = max(d.height, self.height)
}
return d[.top]
})
}
}
struct Demo_Preview: PreviewProvider {
static var previews: some View {
DemoView()
}
}
Edit to show the final results:
I made the changes Asperi suggested, changed the .top alignments to .bottom and got a very nice simple chart:
Here is possible (seems simplest) approach. Tested with Xcode 12 / iOS 14
struct DemoView: View {
var dataSource = [1, 0, 34, 12, 59, 44]
var body: some View {
HStack(alignment: .top) {
Spacer()
/// i want these to all be the same height
ForEach(0 ..< 6) { index in
VStack {
Text("\(index)")
Color.clear
.frame(width: 20, height: CGFloat(dataSource.max() ?? 0))
.overlay(
Color.orange
.frame(height: CGFloat(self.dataSource[index]))
, alignment: .top)
Text("\(dataSource[index])")
}
}
Spacer()
}
}
}

How can I change the colour of separator in list of SwiftUI?

I created a list in SwiftUI. I want to change the color or remove the separator as, In UIKit, we can easily change the color of separator in TableView.
Below is the code and UI(image) of the list in SwiftUI
#State private var users = ["Paul", "Taylor", "Adele"]
var body: some View {
List(){
ForEach(users, id: \.self) { user in
HStack{
Image("helmet").resizable().frame(width: 40, height: 40, alignment: .leading)
VStack(alignment : .leading){
Text(user).font(.custom("SFProText-Semibold", size: 14))
Text("Blu Connect").font(.custom("SFProText-Semibold.ttf", size: 11))
}
}
}
.onDelete(perform: delete)
}
}
Try this
var body: some View {
UITableView.appearance().separatorColor = UIColor.blue
return List(){
ForEach(users, id: \.self) { user in
HStack{
Image("helmet").resizable().frame(width: 40, height: 40, alignment: .leading)
VStack(alignment : .leading){
Text(user).font(.custom("SFProText-Semibold", size: 14))
Text("Blu Connect").font(.custom("SFProText-Semibold.ttf", size: 11))
}
}
}
.onDelete(perform: delete)
}
}
This is a kind of global variable, so you are changing the separator colour in all UITableViews from your app (List and Form are using UITableViews under the hood)
2021 – Xcode 13 / SwiftUI 3
Change color of separator:
.listRowSeparatorTint(Color.pink)
Hide separator:
.listRowSeparator(.hidden)
iOS 15
🌈 From this year, you can apply color to any separator individually with listRowSeparatorTintColor modifier like:
To remove
From iOS 15, you can use listRowSeparator modifier by passing hidden.
Please read this detailed answer for more information and samples.
If you need to do this on older versions of iOS checkout Introspect
You can then do something like the following to change the separator color to red:
List {
// ...
}
.introspectTableView { tableView in
tableView.separatorColor = UIColor.red
}
for new versions see #MojtabaHosseini's answer
You can also swap out the List() element for a ScrollView() element.

Show line / separator view in SwiftUI

I want to show a separator line in my SwiftUI app. To achieve that, I tried to create an empty view with a fixed frame and a background color / border:
EmptyView()
.frame(width: 200, height: 2)
.background(Color.black) // or:
.border(Color.black, width: 2)
Unfortunately, I cannot see any dark view showing up.
Is there a way to show a separator / line view?
Use Divider:
A visual element that can be used to separate other content.
Example:
struct ContentView : View {
var body: some View {
VStack {
Text("Hello World")
Divider()
Text("Hello Another World")
}
}
}
Output:
If anyone is interested a divider, text, divider, looking like this:
LabelledDivider code
struct LabelledDivider: View {
let label: String
let horizontalPadding: CGFloat
let color: Color
init(label: String, horizontalPadding: CGFloat = 20, color: Color = .gray) {
self.label = label
self.horizontalPadding = horizontalPadding
self.color = color
}
var body: some View {
HStack {
line
Text(label).foregroundColor(color)
line
}
}
var line: some View {
VStack { Divider().background(color) }.padding(horizontalPadding)
}
}
It's kind of ugly but I had to put the Dividers into a VStack to make them horizontal, otherwise, they will be vertical, due to HStack. Please let me know if you managed to simplify this :)
Also maybe using and stored properties for LabelledDivider might not be the most SwiftUI-y solution, so I'm open to improvements.
Example usage
This is the code that results in the screenshot seen above:
struct GetStartedView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: SignInView()) {
Text("Sign In").buttonStyleEmerald()
}
LabelledDivider(label: "or")
NavigationLink(destination: SignUpView()) {
Text("Sign up").buttonStyleSaphire()
}
}.padding(20)
}
}
}
ButtonStyle
For sake of completness, I also include buttonStyle view modifiers:
struct ButtonStyle: ViewModifier {
private let color: Color
private let enabled: () -> Bool
init(color: Color, enabled: #escaping () -> Bool = { true }) {
self.color = color
self.enabled = enabled
}
dynamic func body(content: Content) -> some View {
content
.padding()
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
.foregroundColor(Color.white)
.background(enabled() ? color : Color.black)
.cornerRadius(5)
}
}
extension View {
dynamic func buttonStyleEmerald(enabled: #escaping () -> Bool = { true }) -> some View {
ModifiedContent(content: self, modifier: ButtonStyle(color: Color.emerald, enabled: enabled))
}
dynamic func buttonStyleSaphire(enabled: #escaping () -> Bool = { true }) -> some View {
ModifiedContent(content: self, modifier: ButtonStyle(color: Color.saphire, enabled: enabled))
}
}
Edit: Please note that Color.saphire and Color.emerald are custom declared colors:
extension Color {
static var emerald: Color { .rgb(036, 180, 126) }
static var forest: Color { .rgb(062, 207, 142) }
}
extension Color {
static func rgb(_ red: UInt8, _ green: UInt8, _ blue: UInt8) -> Color {
func value(_ raw: UInt8) -> Double {
return Double(raw)/Double(255)
}
return Color(
red: value(red),
green: value(green),
blue: value(blue)
)
}
}
You can just draw a line by using Color. If you want to change the line width or padding, you can use frame or padding like other SwiftUI Components.
//Horizontal Line in VStack
VStack{
Color.gray.frame(height: 1 / UIScreen.main.scale)
}
//Vertical Line in HStack
HStack{
Color.gray.frame(width: 1 / UIScreen.main.scale)
}
If you are looking for a way to customize the divider, there isn't any. You must provide your custom implementation:
struct CustomDivider: View {
let height: CGFloat = 1
let color: Color = .white
let opacity: Double = 0.2
var body: some View {
Group {
Rectangle()
}
.frame(height: height)
.foregroundColor(color)
.opacity(opacity)
}
}
HStack {
VStack {
Divider()
}
Text("or")
.font(.caption)
.foregroundColor(Color(UIColor.systemGray))
VStack {
Divider()
}
}