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?
Related
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()
}
I created a banner modifier that displays a banner from the top. This animates well. However, when I tap to dismiss it, it does not animate at all, just hides even though the tap gesture action has withAnimation wrapping it.
struct BannerModifier: ViewModifier {
#Binding var model: BannerData?
func body(content: Content) -> some View {
content.overlay(
Group {
if model != nil {
VStack {
HStack(alignment: .firstTextBaseline) {
Image(systemName: "exclamationmark.triangle.fill")
VStack(alignment: .leading) {
Text(model?.title ?? "")
.font(.headline)
if let message = model?.message {
Text(message)
.font(.footnote)
}
}
}
.padding()
.frame(minWidth: 0, maxWidth: .infinity)
.foregroundColor(.white)
.background(.red)
.cornerRadius(10)
.shadow(radius: 10)
Spacer()
}
.padding()
.animation(.easeInOut)
.transition(AnyTransition.move(edge: .top).combined(with: .opacity))
.onTapGesture {
withAnimation {
model = nil
}
}
.gesture(
DragGesture()
.onChanged { _ in
withAnimation {
model = nil
}
}
)
}
}
)
}
}
struct BannerData: Identifiable {
let id = UUID()
let title: String
let message: String?
}
In the tap gesture, I wipe out the model but it does not animate. It only hides immediately. How can I animate it so it slide up which is opposite of how it slide down to display? It would be really nice if I can also make the drag gesture interactive so I can slide it out like the native notifications.
Removing view from hierarchy is always animated by container, so to fix your modifier it is needed to apply .animation on some helper container (note: Group is not actually a real container).
Here is corrected variant
struct BannerModifier: ViewModifier {
#Binding var model: BannerData?
func body(content: Content) -> some View {
content.overlay(
VStack { // << holder container !!
if model != nil {
VStack {
HStack(alignment: .firstTextBaseline) {
Image(systemName: "exclamationmark.triangle.fill")
VStack(alignment: .leading) {
Text(model?.title ?? "")
.font(.headline)
if let message = model?.message {
Text(message)
.font(.footnote)
}
}
}
.padding()
.frame(minWidth: 0, maxWidth: .infinity)
.foregroundColor(.white)
.background(Color.red)
.cornerRadius(10)
.shadow(radius: 10)
Spacer()
}
.padding()
.transition(AnyTransition.move(edge: .top).combined(with: .opacity))
.onTapGesture {
withAnimation {
model = nil
}
}
.gesture(
DragGesture()
.onChanged { _ in
withAnimation {
model = nil
}
}
)
}
}
.animation(.easeInOut) // << here !!
)
}
}
Tested with Xcode 12.1 / iOS 14.1 and test view:
struct TestBannerModifier: View {
#State var model: BannerData?
var body: some View {
VStack {
Button("Test") { model = BannerData(title: "Error", message: "Fix It!")}
Button("Reset") { model = nil }
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.modifier(BannerModifier(model: $model))
}
}
This is a slightly improoved version of the banner posted by Asperi
Hope it helps someone.
import SwiftUI
public class BannerData {
public enum BannerType {
case warning, error, success
var textColor: Color {
switch self {
case .warning:
return .black
case .error:
return .white
case .success:
return .white
}
}
var backgroundColor: Color {
switch self {
case .warning:
return .yellow
case .error:
return .red
case .success:
return .green
}
}
var icon: String {
switch self {
case .warning:
return "exclamationmark.triangle.fill"
case .error:
return "exclamationmark.circle.fill"
case .success:
return "checkmark.circle.fill"
}
}
}
var type: BannerType = .success
let title: String
let message: String?
public init(title: String, message: String? = nil, type: BannerType) {
self.title = title
self.message = message
self.type = type
}
}
public struct BannerModifier: ViewModifier {
#Binding var model: BannerData?
public init(model: Binding<BannerData?>) {
_model = model
}
public func body(content: Content) -> some View {
content.overlay(
VStack {
if model != nil {
VStack {
HStack(alignment: .firstTextBaseline) {
Image(systemName: model?.type.icon ?? BannerData.BannerType.success.icon)
VStack(alignment: .leading) {
Text(model?.title ?? "")
.font(.headline)
if let message = model?.message {
Text(message)
.font(.footnote)
}
}
Spacer()
}
.padding()
.frame(minWidth: 0, maxWidth: .infinity)
.foregroundColor(.white)
.background(model?.type.backgroundColor ?? .clear)
.cornerRadius(10)
.shadow(radius: 10)
Spacer()
}
.padding()
.transition(AnyTransition.move(edge: .top).combined(with: .opacity))
.onTapGesture {
withAnimation {
model = nil
}
}
.gesture(
DragGesture()
.onChanged { _ in
withAnimation {
model = nil
}
}
)
}
}
.animation(.spring())
)
}
}
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
}
}
}
Trying to work with SwiftUI and experiencing a weird issue. I have a List of Views that should be clickable to push to a detail view, swipable to delete, and have a couple of interactive elements in the view. The list is more or less set up as follows:
NavigationView {
ZStack(alignment: .top) {
if viewModel.value != nil {
List {
ForEach(viewModel.value!, id: \.address) { model in
VStack {
NavigationLink(destination: AliasDetailView(alias: model) { msg in
self.toastText = msg
self.showCopiedToast = true
}) {
ModelRow(alias: alias) { msg in
self.toastText = msg
self.showCopiedToast = true
}
}
}
}
.onDelete(perform: self.deleteModel)
}
.listStyle(PlainListStyle())
.alert(isPresented: $showDefaultAlert) {
Alert(title: Text("Default Address"),
message: Text("Would you like to set this as your default address?"),
primaryButton: .default(Text("Yes")) {
NSUbiquitousKeyValueStore.default.set(self.targetAddress, forKey: SettingsKeys.DefaultAddress)
NSUbiquitousKeyValueStore.default.synchronize()
print("Stored key")
self.targetAddress = ""
}, secondaryButton: .cancel({
NSUbiquitousKeyValueStore.default.set("NONE", forKey: SettingsKeys.DefaultAddress)
NSUbiquitousKeyValueStore.default.synchronize()
self.targetAddress = ""
}))
}
} else {
Text("Loading...")
}
VStack {
Spacer()
.alert(isPresented: $showInvalidAddressAlert) {
Alert(title: Text("Invalid Email"), message: Text("Please enter a valid address."), dismissButton: .default(Text("Ok")))
}
// Begin Floating action button
HStack {
Spacer()
Button(action: {
addModelAction()
}, label: {
Text("+")
.font(.largeTitle)
.frame(width: 77, height: 70)
.foregroundColor(.white)
.padding(.bottom, 7)
})
.background(Color.blue)
.cornerRadius(38.5)
.padding()
.shadow(color: Color.black.opacity(0.3),
radius: 3, x: 3, y: 3)
.alert(isPresented: $showAccountLimitAlert) {
Alert(title: Text("Account Limit Reached"), message: Text("You are currently at your account limit for models. Upgrade your account to create more."), dismissButton: .default(Text("Ok")))
}
}
}
}
.navigationBarTitle("Models")
.navigationBarItems(trailing:
Button(action: {
self.showSettingsModal = true
}) {
Image(systemName: "gearshape")
.font(.title)
}
)
.addPartialSheet(style: PartialSheetStyle(
backgroundColor: .black,
handlerBarColor: .secondary,
enableCover: true,
coverColor: Color.black.opacity(0.6),
blurEffectStyle: .dark))
.popup(isPresented: $showCopiedToast, type: .toast, position: .bottom, autohideIn: 2) {
HStack {
Text(self.toastText)
}
.frame(width: 200, height: 60)
.background(Color(red: 0.85, green: 0.8, blue: 0.95))
.cornerRadius(30.0)
.padding(15)
}
.sheet(isPresented: $showSettingsModal) {
SettingsView()
.environmentObject(ProductsStore.shared)
}
I've included everything in the body because I have a suspicion its related to something else blocking the touches. I've tried removing some things but can't seem to identify it. Here is the view code for the row:
HStack {
VStack(alignment: .leading) {
HStack {
VStack(alignment: .leading) {
Text(model.address)
.font(.headline)
.lineLimit(1)
.truncationMode(.tail)
Text("➜ \(model.target)")
.font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
VStack {
Image(systemName: "doc.on.doc")
.frame(width: 35, height: 38)
.font(.headline)
.foregroundColor(.accentColor)
.onTapGesture {
copyAlias()
}
}
}
HStack {
Text("Received: \(model.received)")
Spacer()
Toggle("", isOn: $isActive)
.labelsHidden()
.onReceive([self.isActive].publisher.first()) { value in
if value != prevActive {
toggleModel()
}
}
}
}
}
Also note: The list view does work if you hold the elements. i.e. Tapping a row will not trigger the navigation link but holding the cell will eventually trigger it.
Turns out it was the Toast Library I was using. Likely however their view modifier works its capturing touches.
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()
}
}