SwiftUI Form Icons - ios

Sorry if this is a really stupid question and maybe offtopic, but I can't seem to find it anywhere.
I'm trying to do a simple settings section for my app and would like to add icons to my elements so users can easily understand what does each setting does. To achieve that, I used Horizontal Stacks (HStack) with an Image and a Label (Text).
This somehow does the trick but I'd like to know if there's a cleaner way to do this.
I'd also like to know how to adjust the separator between cells to stop on the label, and not continue until the end of the element.
As I'm not really good at explaining, here you have two images:
This is what I got
I'd like to have something similar to this
My code:
SettingsView.swift
struct SettingsView: View {
#State var age: Int = 0
var body: some View {
UITableView.appearance().separatorStyle = .singleLine
UINavigationBar.appearance().shadowImage = UIImage()
return NavigationView {
Form {
Section {
Picker(selection: .constant(1), label: HStack {
Image(systemName: "paintbrush")
.font(Font.system(size: 25, weight: .light))
Text("Editor Theme")
}) {
Text("Ayu Mirage").tag(1)
Text("Ayu Light").tag(2)
}
Stepper(value: self.$age,
in: 18...100,
label: {
HStack {
Image(systemName: "rectangle.stack")
.font(Font.system(size: 25, weight: .light))
Text("Number of snippets: \(self.age)")
}
})
NavigationLink(destination: NotificationSettingsView()) {
HStack {
Image(systemName: "app.badge")
.font(Font.system(size: 25, weight: .light))
Text("Notifications")
}
}
}
Section {
VStack(alignment: .leading, spacing: 5) {
Text("Mercury v1.0")
.font(.headline)
Text("Designed with ❤️ by amodrono")
.font(.footnote)
}.padding(.vertical, 5)
}
}
.navigationBarTitle("Hello")
.environment(\.horizontalSizeClass, .regular)
.navigationBarTitle(Text("Settings"), displayMode: .inline)
.navigationBarItems(leading: (
Button(action: {
}) {
Image(systemName: "xmark")
.font(.headline)
.imageScale(.large)
.foregroundColor(Color(UIColor(named: "adaptiveColor")!))
}
))
}
}
}

You can disable the separator from the tableview and add your own divider.
Something like this:
struct ContentView: View {
var body: some View {
UITableView.appearance().separatorStyle = .none
return NavigationView {
Form {
Section {
RowView(iconName:"rectangle.stack", text:"Some text")
RowView(iconName:"paintbrush", text:"Some other text", showDivider: false)
}
}
}
}
}
struct RowView: View {
var iconName: String
var text: String
var showDivider = true
var body: some View {
HStack(alignment: .firstTextBaseline) {
Image(systemName: iconName)
VStack(alignment: .leading) {
Text(text)
if showDivider {
Divider()
} else {
Spacer()
}
}
}
}
}

do you mean like this? (if yes, just switch darkmode on ;))
import SwiftUI
#available(iOS 13.0, *)
public struct DarkView<Content> : View where Content : View {
var darkContent: Content
var on: Bool
public init(_ on: Bool, #ViewBuilder content: () -> Content) {
self.darkContent = content()
self.on = on
}
public var body: some View {
ZStack {
if on {
Spacer()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.black)
.edgesIgnoringSafeArea(.all)
darkContent.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).background(Color.black).colorScheme(.dark)
} else {
darkContent
}
}
}
}
#available(iOS 13.0, *)
extension View {
#available(iOS 13.0, *)
public func darkModeFix(_ on: Bool = true) -> DarkView<Self> {
DarkView(on) {
self
}
}
}
struct ContentView: View {
#State var age: Int = 0
var body: some View {
UITableView.appearance().separatorStyle = .singleLine
UINavigationBar.appearance().shadowImage = UIImage()
return NavigationView {
Form {
Section {
Picker(selection: .constant(1), label: HStack {
Image(systemName: "paintbrush")
.font(Font.system(size: 25, weight: .light))
Text("Editor Theme")
}) {
Text("Ayu Mirage").tag(1)
Text("Ayu Light").tag(2)
}
Stepper(value: self.$age,
in: 18...100,
label: {
HStack {
Image(systemName: "rectangle.stack")
.font(Font.system(size: 25, weight: .light))
Text("Number of snippets: \(self.age)")
}
})
// NavigationLink(destination: NotificationSettingsView()) {
// HStack {
// Image(systemName: "app.badge")
// .font(Font.system(size: 25, weight: .light))
//
// Text("Notifications")
// }
// }
}
Section {
VStack(alignment: .leading, spacing: 5) {
Text("Mercury v1.0")
.font(.headline)
Text("Designed with ❤️ by amodrono")
.font(.footnote)
}.padding(.vertical, 5)
}
}
.navigationBarTitle("Hello")
.environment(\.horizontalSizeClass, .regular)
.navigationBarTitle(Text("Settings"), displayMode: .inline)
.navigationBarItems(leading: (
Button(action: {
}) {
Image(systemName: "xmark")
.font(.headline)
.imageScale(.large)
// .foregroundColor(Color(UIColor(named: "adaptiveColor")!))
}
))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.colorScheme, .dark)
.darkModeFix()
}
}

Related

SwiftUI ScrollView won't let me scroll up

I'm trying to implement a chat feature, the relevant codes are simple enough
struct ContentView: View {
#State private var channelId = ""
#State private var message = ""
#ObservedObject var sig = SignalRService(urlStr: "\(API.HubsEndpoint)/hubs/theHub")
var body: some View {
VStack {
HStack {
TextField("Channel ID", text: $channelId) .textFieldStyle(.roundedBorder)
Button {
sig.invoke(method: "JoinRoom", arguments: [channelId]) { error in
print("joined")
if let err = error {
print(err)
}
}
} label: {
Text("Join")
}
.padding(5)
.background(Color.accentColor)
.foregroundColor(.white)
.cornerRadius(2)
}
.padding()
Spacer()
ScrollView {
ScrollViewReader { scrollView in
LazyVStack(alignment: .leading, spacing: 5) {
ForEach(sig.messages) { msg in
HStack(alignment: .top) {
Text(msg.user)
.font(.caption)
.bold()
Text(msg.message)
.font(.caption)
}
.id(msg.id)
.padding(20)
.background(.regularMaterial)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.onChange(of: sig.messages.last) { newValue in
scrollView.scrollTo(newValue?.id)
}
}
.frame(height: UIScreen.main.bounds.height/4, alignment:.bottom)
}
.frame(height: UIScreen.main.bounds.height/4, alignment:.bottom)
.background(Color.black)
HStack {
TextField("Message", text: $message)
.textFieldStyle(.roundedBorder)
Button {
sig.invoke(method: "SendMessage", arguments: [message, channelId]) { error in
print("messaged")
if let err = error {
print(err)
}
else {
message = ""
}
}
} label: {
Image(systemName: "paperplane.fill")
}
.padding(5)
.background(Color.accentColor)
.foregroundColor(.white)
.cornerRadius(2)
}
.padding(.horizontal)
}
}
}
And the result: https://imgur.com/a/9JdPkib
It kept snapping to the bottom when I tried to scroll up.
I'm clipping the frame size and increased the padding to exaggerate the views, so it's easier to test.
Any idea what's causing the issue?

Moving to another view without NavigationView

I have NavigationView in ContentView
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
BarView()
AdsView()
SelectTypeVideo()
Spacer()
}
.ignoresSafeArea(.all)
}
}
}
I can't use it either in a sub-struct like BarView()
I tried to use the first
if isSearch {
SearchBar()
}
or
.onTapGesture {
isSearch.toggle()
}
Doesn't respond when pressed
I want to move from the BarView() to the SearchBar()
struct BarView: View {
#State var isSearch = false
var body: some View {
if isSearch {
SearchBar()
}
HStack {
ZStack {
HStack {
Button(action: { isSearch.toggle() }) {
ZStack {
Rectangle()
.fill(Color.white)
.cornerRadius(10)
.shadow(color: .black.opacity(0.2), radius: 10)
.frame(maxWidth: 35, maxHeight: 35)
.padding(.top, 25)
Image(systemName: "gear")
.padding(.top, 25).padding()
.foregroundColor(Color.black)
}
}
}
}
Spacer()
Text("other")
.padding(.top, 30).padding()
.font(.title)
}.padding(.top, 10)
}
}
Sorry if the question is simple, but I didn't find a solution
Remove
if isSearch {
SearchBar()
}
from BarView and place it in ContentView like this
if isSearch {
SearchBar()
} else {
BarView()
}

On tap some row in List with a button the action is triggered even clicking outside the button area

I'm creating a home screen with a list of custom components. These componentes are one or more click areas with navigation link or buttons, like the above image:
App Home
But the click area is not respected, if I click in any space in the row the click is triggered even it's outside the button/navigation link area
Click buttom area
Click bug video
The code is available in https://github.com/isaquedev/swiftui-list-click-bug and bellow:
Home
import SwiftUI
struct ContentView: View {
var items: [HomeSection] = [
HomeSection(title: "Mais próximos", type: .nearest),
HomeSection(title: "", type: .list, items: [
SectionItem(title: "Românticos", cover: "prato-1"),
SectionItem(title: "Especiais", cover: "prato-2"),
SectionItem(title: "Agradáveis", cover: "prato-1"),
SectionItem(title: "Interessantes", cover: "prato-2"),
])
]
var body: some View {
VList {
ForEach(items) { item in
switch item.type {
case .nearest:
NearestCell(title: item.title)
case .list:
ListCell(items: item.items ?? [])
}
}
}
}
}
VList
import SwiftUI
struct VList<Content:View>: View {
var content: () -> Content
var body: some View {
if #available(iOS 14.0, *) {
ScrollView {
LazyVStack(spacing: 0) {
content()
}
}
} else {
List {
content()
.padding(.horizontal, -20)
}
.onAppear {
UITableView.appearance().separatorStyle = .none
UITableViewCell.appearance().selectionStyle = .none
}
}
}
}
NearestCell
import SwiftUI
struct NearestCell: View {
var title: String
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.title)
.padding(.bottom, 16)
VStack {
Text("Veja restaurantes a sua volta")
Button(action: {
print("Click")
}, label: {
Text("Visualizar")
})
.padding(.top, 16)
}
.frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .center)
.padding(.vertical, 16)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.black, lineWidth: 1)
)
}
.padding(.all, 16)
.frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .leading)
}
}
ListCell
import SwiftUI
struct ListCell: View {
var items: [SectionItem]
var body: some View {
VStack(alignment: .leading) {
Text("Categorias")
.font(.title)
ScrollView(.vertical, showsIndicators: false) {
HStack {
ForEach(items) { item in
VStack {
Image(item.cover)
.resizable()
.scaledToFill()
.frame(width: 80, height: 80, alignment: .center)
.clipped()
Text(item.title)
}
}
}
}
.frame(height: 113)
}
.padding(.all, 16)
}
}
Home Section
enum HomeSectionType {
case nearest, list
}
class HomeSection: Identifiable {
let id = UUID()
var title: String
var type: HomeSectionType
var items: [SectionItem]?
init(title: String, type: HomeSectionType, items: [SectionItem]? = nil) {
self.title = title
self.type = type
self.items = items
}
}
SectionItem
class SectionItem: Identifiable {
let id = UUID()
var title: String
var cover: String
init(title: String, cover: String) {
self.title = title
self.cover = cover
}
}
There is no problem with your code, it is all because of List, List apply action of a Button of a row to all row, for solving the issue use just Text and put action code inside onTapGesture.
struct NearestCell: View {
var title: String
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.title)
.padding(.bottom, 16)
VStack {
Text("Veja restaurantes a sua volta")
Text("Visualizar") // <<: here
.foregroundColor(Color.blue)
.padding(.top, 16)
.onTapGesture { print("Click") } // <<: here
}
.frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .center)
.padding(.vertical, 16)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.black, lineWidth: 1)
)
}
.padding(.all, 16)
.frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .leading)
}
}

How can I add a badge to a leading navigationBarItems in SwiftUI and iOS 14?

How can I add a badge to navigationBarItems in SwiftUI and iOS 14?
I cannot find anything on the net...
I want for example add a badge over the leading navigationBarItems:
var body: some View {
NavigationView {
ZStack {
VStack(spacing: 0) {
Text("Peanut")
.padding(-10)
.navigationBarTitle(Text("HomeTitle"), displayMode: .inline)
.navigationBarItems(leading:
HStack {
NavigationLink(destination: Notifications()) {
Image(systemName: "bell")
.font(.system(size: 20))
}.foregroundColor(.white)
}, trailing:
HStack {
NavigationLink(destination: Settings()) {
Image(systemName: "gearshape")
.font(.system(size: 20))
}.foregroundColor(.white)
})
}
}
}
}
You can create a custom Badge view:
struct Badge: View {
let count: Int
var body: some View {
ZStack(alignment: .topTrailing) {
Color.clear
Text(String(count))
.font(.system(size: 16))
.padding(5)
.background(Color.red)
.clipShape(Circle())
// custom positioning in the top-right corner
.alignmentGuide(.top) { $0[.bottom] }
.alignmentGuide(.trailing) { $0[.trailing] - $0.width * 0.25 }
}
}
}
and use it as an overlay:
struct ContentView: View {
var body: some View {
NavigationView {
ZStack {
VStack(spacing: 0) {
Text("Peanut")
.padding(-10)
.navigationBarTitle(Text("HomeTitle"), displayMode: .inline)
.navigationBarItems(leading: leadingBarItems)
}
}
}
}
var leadingBarItems: some View {
NavigationLink(destination: Text("Notifications")) {
Image(systemName: "bell")
.font(.system(size: 20))
}
.foregroundColor(.primary)
.padding(5)
.overlay(Badge(count: 3))
}
}
Note
The badge view uses alignment guides for positioning. For more information see:
Alignment Guides in SwiftUI
Here's another example of custom badge
struct BadgeViewModifier: ViewModifier {
let text: String?
func body(content: Content) -> some View {
content
.overlay(alignment: .topTrailing) {
text.map { value in
Text(value)
.fixedSize(horizontal: true, vertical: false)
.font(.system(size: 14, weight: .semibold))
.foregroundColor(DS.Colors.white)
.padding(.horizontal, value.count == 1 ? 2 : 6)
.padding(.vertical, 2)
.background(
Capsule()
.fill(DS.Colors.red)
.if(value.count == 1) { $0.aspectRatio(1, contentMode: .fill) }
)
}
}
}
}
extension View {
func badge(value: String?) -> some View {
modifier(BadgeViewModifier(text: value))
}
#ViewBuilder func `if`<Result: View>(_ condition: Bool, closure: #escaping (Self) -> Result) -> some View {
if condition {
closure(self)
} else {
self
}
}
}

SwiftUI align navigationBarItems buttons with large navigationBarTitle like in "Messages"

I am creating a SwiftUI app and I am using NavigationView and a large title bar. However, I don't like that navigationBarItems buttons are not aligned with the large title bar (3rd picture) like in the Messages app (first and second picture). I tried to reposition the button, but it wasn't clickable anymore. Does anybody have an idea how to solve this? Thanks!
2nd:
3rd:
Solution: (found here: https://www.hackingwithswift.com/forums/swiftui/icons-in-navigationview-title-apple-messages-style/592)
import SwiftUI
struct ContentView: View {
#State private var midY: CGFloat = 0.0
#State private var headerText = "Contacts"
var body: some View {
NavigationView {
List {
HStack {
//text
HeaderView(headerText: self.headerText, midY: $midY)
.frame(height: 40, alignment: .leading)
.padding(.top, 5)
.offset(x: -45)
HStack {
//button 1
Button(action: {
self.action1()
}) {
Image(systemName: "ellipsis.circle")
.font(.largeTitle)
}
//button 2
Button(action: {
self.action2()
}) {
Image(systemName: "pencil.circle")
.font(.largeTitle)
}
}.padding(EdgeInsets(top: 5, leading: 0, bottom: 0, trailing: 16))
.foregroundColor(.blue)
} .frame(height: 40, alignment: .leading)
.opacity(self.midY < 70 ? 0.0 : 1.0)
.frame(alignment: .bottom)
ForEach(0..<100){ count in
Text("Row \(count)")
}
}
.navigationBarTitle(self.midY < 70 ? Text(self.headerText) : Text(""), displayMode: .inline)
.navigationBarItems(trailing: self.midY < 70 ? HStack {
//button 1
Button(action: {
self.action1()
}) {
Image(systemName: "ellipsis.circle")
.frame(width: 20, height: 20)
}
//button 2
Button(action: {
self.action2()
}) {
Image(systemName: "pencil.circle")
.frame(width: 20, height: 20)
}
}
:
HStack {
//button 1
Button(action: {
self.action1()
}) {
Image(systemName: "ellipsis.circle")
.frame(width: 0, height: 0)
}
//button 2
Button(action: {
self.action2()
}) {
Image(systemName: "pencil.circle")
.frame(width: 0, height: 0)
}
}
)
}
}
func action1() {
print("do action 1...")
}
func action2() {
print("do action 2...")
}
}
struct HeaderView: View {
let headerText: String
#Binding var midY: CGFloat
var body: some View {
GeometryReader { geometry -> Text in
let frame = geometry.frame(in: CoordinateSpace.global)
withAnimation(.easeIn(duration: 0.25)) {
DispatchQueue.main.async {
self.midY = frame.midY
}
}
return Text(self.headerText)
.bold()
.font(.largeTitle)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
.navigationBarItems(leading:
Button(action: goBack) { HStack { Image("back") } })
.navigationBarItems(trailing: HStack {
Button(action: { self.share() }) { Image("share") }
Button(action: { self.wishlist() }) { Image("wishlist_detail") }})

Resources