SwiftUI: Random "Extra argument in call" error - swiftui

So I'm trying to learn SwiftUI and Combine. I usually start new tech by making a simple tip calculator.
I seem to be getting a random "Extra argument in call." error while coding
Here is my SwiftUI File
import SwiftUI
internal enum ReceiptRowType {
case subtotal
case tax
case total
case tip
case grandTotal
}
struct TipView: View {
#ObservedObject internal var adBannerView: BannerAdView = BannerAdView()
#ObservedObject internal var receiptViewModel: ReceiptViewModel
private let percentageFormatter: NumberFormatter = {
let f = NumberFormatter()
f.numberStyle = .percent
return f
}()
var body: some View {
ZStack {
Color.white
.scaledToFit()
VStack {
if adBannerView.adHasLoaded {
adBannerView
.frame(maxHeight: adBannerView.adHeight)
.animation(.easeInOut(duration: 2.0))
}
BorderView()
Text(ARCHLocalizedStrings.receipt)
.foregroundColor(Color.gray)
BorderView()
HStack {
Spacer()
Button(action: {
self.receiptViewModel.addNewReceiptItem()
}) {
Text(ARCHLocalizedStrings.buttonTitleAddItem)
}
}
BorderView()
ScrollView {
ForEach(receiptViewModel.receiptItems) { receiptItem in
ItemView(receiptItem: receiptItem)
if receiptItem != self.receiptViewModel.receiptItems.last {
Divider()
}
}
}
BorderView()
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.subtotal,
title: ARCHLocalizedStrings.subtotal)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.tax,
title: ARCHLocalizedStrings.tax)
}
.padding(.horizontal, ARCHSwiftUILayoutConstants.defaultPaddingAndSpacing)
}
}
}
struct BorderView: View {
var body: some View {
Text("================================")
.lineLimit(1)
.foregroundColor(Color.gray)
.minimumScaleFactor(0.5)
}
}
struct ItemView: View {
#ObservedObject var receiptItem: ReceiptItemViewModel
var body: some View {
HStack {
TextField(receiptItem.name, text: $receiptItem.name)
.textFieldStyle(RoundedBorderTextFieldStyle())
.foregroundColor(Color.gray)
.multilineTextAlignment(TextAlignment.leading)
TextField("Price", value: $receiptItem.price, formatter: ARCHUtilities.currencyFormatter)
.textFieldStyle(RoundedBorderTextFieldStyle())
.foregroundColor(Color.gray)
.multilineTextAlignment(TextAlignment.trailing)
.minimumScaleFactor(0.5)
.frame(width: ARCHSwiftUILayoutConstants.widthForCurrency)
}
}
}
struct BottomOfReceiptRow: View {
#ObservedObject internal var receiptViewModel: ReceiptViewModel
internal var type: ReceiptRowType
internal var title: String
var body: some View {
HStack {
Spacer()
Text(title)
.foregroundColor(Color.gray)
if type == ReceiptRowType.subtotal {
Text("\(receiptViewModel.subtotal)")
.foregroundColor(Color.gray)
.frame(width: ARCHSwiftUILayoutConstants.widthForCurrency)
} else if type == ReceiptRowType.tax {
Text("\(receiptViewModel.taxRate)")
.foregroundColor(Color.gray)
.frame(width: ARCHSwiftUILayoutConstants.widthForCurrency)
} else if type == ReceiptRowType.total {
Text("\(receiptViewModel.total)")
.foregroundColor(Color.gray)
.frame(width: ARCHSwiftUILayoutConstants.widthForCurrency)
} else if type == ReceiptRowType.tip {
} else if type == ReceiptRowType.grandTotal {
Text("\(receiptViewModel.grandTotal)")
.foregroundColor(Color.gray)
.frame(width: ARCHSwiftUILayoutConstants.widthForCurrency)
}
}
}
}
struct TipView_Previews: PreviewProvider {
static var previews: some View {
TipView(receiptViewModel: ReceiptViewModel())
}
}
However, if I add another view on the TipView body (Any View) I seem to get a "Extra argument in call" error.
Picture of error here
Does anyone know what's going on?

try making a Group { } around your views. just 10 are allowed in Swiftui...with group you can add more. or use subviews...(would be cleaner code too)

The #ViewBuilder system in SwiftUI is limited to 10 views within any given view container. There's no argument for an 11th view, so you get that error.
Basic solution:
The issue here is that your VStack is at maximum capacity, however you can wrap those existing views inside of other containers. As a very basic example that would allow you to show 10 BottomReceiptRow views:
var body: some View {
ZStack {
Color.white
.scaledToFit()
// Modifications start here
VStack {
if adBannerView.adHasLoaded {
adBannerView
.frame(maxHeight: adBannerView.adHeight)
.animation(.easeInOut(duration: 2.0))
}
BorderView()
Text(ARCHLocalizedStrings.receipt)
.foregroundColor(Color.gray)
BorderView()
HStack {
Spacer()
Button(action: {
self.receiptViewModel.addNewReceiptItem()
}) {
Text(ARCHLocalizedStrings.buttonTitleAddItem)
}
}
BorderView()
ScrollView {
ForEach(receiptViewModel.receiptItems) { receiptItem in
ItemView(receiptItem: receiptItem)
if receiptItem != self.receiptViewModel.receiptItems.last {
Divider()
}
}
}
BorderView()
Group {
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.subtotal,
title: ARCHLocalizedStrings.subtotal)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.tax,
title: ARCHLocalizedStrings.tax)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.someValue,
title: ARCHLocalizedStrings.someValue)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.someValue2,
title: ARCHLocalizedStrings.someValue2)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.someValue3,
title: ARCHLocalizedStrings.someValue3)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.someValue4,
title: ARCHLocalizedStrings.someValue4)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.someValue5,
title: ARCHLocalizedStrings.someValue5)
}
// Modifications end here
}
.padding(.horizontal, ARCHSwiftUILayoutConstants.defaultPaddingAndSpacing)
}
}
Alternatively:
You may prefer to completely refactor these sections into their own View as composition makes the code easier to read. It's not strictly necessary here though.
If you did want to do that, you could consider an example where those receipt rows are all in their own view like:
var body: some View {
ZStack {
Color.white
.scaledToFit()
VStack {
if adBannerView.adHasLoaded {
adBannerView
.frame(maxHeight: adBannerView.adHeight)
.animation(.easeInOut(duration: 2.0))
}
BorderView()
Text(ARCHLocalizedStrings.receipt)
.foregroundColor(Color.gray)
BorderView()
HStack {
Spacer()
Button(action: {
self.receiptViewModel.addNewReceiptItem()
}) {
Text(ARCHLocalizedStrings.buttonTitleAddItem)
}
}
BorderView()
ScrollView {
ForEach(receiptViewModel.receiptItems) { receiptItem in
ItemView(receiptItem: receiptItem)
if receiptItem != self.receiptViewModel.receiptItems.last {
Divider()
}
}
}
BorderView()
bottomRow
}
.padding(.horizontal, ARCHSwiftUILayoutConstants.defaultPaddingAndSpacing)
}
}
// Additional computed property in TipView
var bottomRow: some View {
Group {
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.subtotal,
title: ARCHLocalizedStrings.subtotal)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.tax,
title: ARCHLocalizedStrings.tax)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.someValue,
title: ARCHLocalizedStrings.someValue)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.someValue2,
title: ARCHLocalizedStrings.someValue2)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.someValue3,
title: ARCHLocalizedStrings.someValue3)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.someValue4,
title: ARCHLocalizedStrings.someValue4)
BottomOfReceiptRow(receiptViewModel: receiptViewModel,
type: ReceiptRowType.someValue5,
title: ARCHLocalizedStrings.someValue5)
}
} // end bottomRow

In SwiftUI you can stack up to 10 views in the same Stack (VStack, HStack, ZStack), so you can Embed some Views inside another Stack or inside Group {}.

Related

How to add button in custom View (ListRowView) without providing prior functionality, in Swift UI?

Code below is working perfectly but i have an issue, i don't want to provide functionality here, i just want to add button (dropDownButton) and provide the functionality when using this custom view.
struct DrawerItemListRowView: View {
#State var iconName: Icon
#State var text: String
#State var dropDownButton = Button(action: {}) {
Image(icon: .drawer)
}
var body: some View {
HStack(alignment: .center, spacing: 15) {
Image(icon: iconName)
Text(text)
.foregroundColor(.customLightBlack)
.font(.custom(ubuntu: .regular, style: .title2))
Spacer()
dropDownButton
.frame(width: 24, height: 24, alignment: .trailing)
}
.padding()
.listRowSeparator(.hidden)
.listRowBackground(Color.customBackground)
.background(Color.clear)
}
}
struct DrawerItemListRowView_Previews: PreviewProvider {
static var previews: some View {
Group {
DrawerItemListRowView(iconName: .mainCategory, text: "Shop by category")
DrawerItemListRowView(iconName: .paymentMethod, text: "Payment Methods")
}
.previewLayout(.sizeThatFits)
.background(.white)
} }
You need to pass the action, not the button, as of type ()->Void.
Check out this example:
struct DrawerItemListRowView: View {
let iconName: String
let text: String
let action: ()->Void // Pass the action, not the button
var body: some View {
HStack(alignment: .center, spacing: 15) {
Image(systemName: iconName)
Text(text)
.foregroundColor(.gray)
Spacer()
Button {
action() // Call the action
} label: {
Text(text)
.fixedSize()
}
.frame(width: 24, height: 24, alignment: .trailing)
}
.padding()
.listRowSeparator(.hidden)
.listRowBackground(Color.yellow)
.background(Color.clear)
}
}
struct Example: View {
var body: some View {
VStack {
DrawerItemListRowView(iconName: "house", text: "Shop by category") {
print("Bought")
}
DrawerItemListRowView(iconName: "minus", text: "Payment Methods") {
print("Paid")
}
}
.previewLayout(.sizeThatFits)
.background(.white)
}
}

showing TextField in Alert function at SwiftUi

I am new in SwiftUi and I am trying to show Alert message with TextField and action button, I want to use Alert function for this, but I can't add TextField in Alert function,I shared code at below, how can I add TextField in Alert function? thanks..
.alert(isPresented:$showAlertDialog) {
Alert(
title: Text("Please Enter sended E-mail Code"),
message: Text("There is no undo"),
primaryButton: .destructive(Text("Submit")) {
print("Deleting...")
},
secondaryButton: .cancel()
)
}
As of iOS 15 alerts are not capable of showing TextFields. But you can make a custom SwiftUI alert.
struct TextFieldAlert: ViewModifier {
#Binding var isPresented: Bool
let title: String
#Binding var text: String
let placeholder: String
let action: (String) -> Void
func body(content: Content) -> some View {
ZStack(alignment: .center) {
content
.disabled(isPresented)
if isPresented {
VStack {
Text(title).font(.headline).padding()
TextField(placeholder, text: $text).textFieldStyle(.roundedBorder).padding()
Divider()
HStack{
Spacer()
Button(role: .cancel) {
withAnimation {
isPresented.toggle()
}
} label: {
Text("Cancel")
}
Spacer()
Divider()
Spacer()
Button() {
action(text)
withAnimation {
isPresented.toggle()
}
} label: {
Text("Done")
}
Spacer()
}
}
.background(.background)
.frame(width: 300, height: 200)
.cornerRadius(20)
.overlay {
RoundedRectangle(cornerRadius: 20)
.stroke(.quaternary, lineWidth: 1)
}
}
}
}
}
extension View {
public func textFieldAlert(
isPresented: Binding<Bool>,
title: String,
text: Binding<String>,
placeholder: String = "",
action: #escaping (String) -> Void
) -> some View {
self.modifier(TextFieldAlert(isPresented: isPresented, title: title, text: text, placeholder: placeholder, action: action))
}
}
Use it on the root view. (don't attach it to Buttons to other inner elements for example). This will ensure that presenting view is disabled.
Sample usage:
struct ContentView: View {
#State var isAlertDisplayed = false
#State var text = "Text to change"
var body: some View {
VStack {
Text("Press the button to show the alert").multilineTextAlignment(.center)
Text("Current value is: \(text)")
Button("Change value") {
withAnimation {
isAlertDisplayed = true
}
}
.padding()
}
.textFieldAlert(isPresented: $isAlertDisplayed, title: "Some title", text: $text, placeholder: "Placeholder", action: { text in
print(text)
})
.padding()
}
}

SwiftUi line pass with a hstack

Hello to all, I have a simple question yet I find not that solutions that seems very complex.
I am looking for a way to make a line break on my Hstack once the screen size is exceeded.
Here is my code:
//
// TrendySection.swift
// Activiteam
//
// Created by Theo Marie on 30/04/2022.
//
struct IdentifiableSport: Identifiable {
var id = UUID()
var name: String
var icon: String
}
import SwiftUI
struct TrendySection: View {
var body: some View {
let trendySports = [
IdentifiableSport(name: "Tennis", icon: "tennis"),
IdentifiableSport(name: "Badminton", icon: "badminton"),
IdentifiableSport(name: "Basketball", icon: "basketball"),
IdentifiableSport(name: "Football", icon: "football"),
]
VStack(spacing: 12) {
Text("Activités les plus recherchées")
.font(Font.custom("Poppins-Bold", size: 24))
.foregroundColor(Color.black)
.frame(maxWidth: 300, alignment: .topLeading)
.lineLimit(2)
HStack(spacing: 32) {
ForEach(trendySports) { trendySport in
Trendybutton(icon: trendySport.icon, sportName: trendySport.name)
}
}
.padding([.top, .bottom, .leading, .trailing], 0)
}
}
}
struct TrendySection_Previews: PreviewProvider {
static var previews: some View {
TrendySection()
}
}
My current rendering
the Figma model
I thank you in advance for your proposal.
As someone suggested, you should try to checkout LazyVStack or LazyHStack. Here is some code that may help you.
struct ContentView: View {
let items = [GridItem(.flexible()), GridItem(.flexible()),GridItem(.flexible())]
let data = (1...20).map { "Item #\($0)" }
var body: some View {
LazyVGrid(columns: items) {
ForEach(data, id: \.self) { item in
Text(item)
.padding()
.background(.green)
}
}
}
}

Why the scrollview doesn't get updated with new data from array?

I'm trying to send and then display them in the scrollview realtime. But nothing shows up. How to solve it? So, basically when the user types the message into a textbox then it will be saved in array and then it will be populated to the crollView in realtime so the user can view all the messages.
Error: No errors, it just isn't visible.
import SwiftUI
struct SingleMessageBubbleModel: Identifiable {
let id = UUID()
var text: String
var received: Bool
var timeStamp: Date
}
var messagesDBArray : [SingleMessageBubbleModel] = []
struct ContentView: View {
#State private var showOnTheSpotMessaging: Bool = true
#State var textTyped: String
var body: some View {
if (showOnTheSpotMessaging) {
VStack {
HStack {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(messagesDBArray, id: \.id) { message in
MessageBubble(message: message)
}
}
}
.padding(.top, 10)
.background(.gray)
.onChange(of: messagesDBArray.count) { id in
withAnimation {
proxy.scrollTo(id, anchor: .bottom)
}
}
}
.frame( height: 200, alignment: .bottomLeading)
}
HStack () {
TextEditor (text: $textTyped)
.frame(width: 200, height: 200, alignment: .leading)
Button ("Send", action: {
messagesDBArray.append(SingleMessageBubbleModel(text: textTyped, received: true, timeStamp: Date()))
})
}
}
}
}
}
struct MessageBubble: View {
var message: SingleMessageBubbleModel
#State private var showTime = false
var body: some View {
VStack(alignment: message.received ? .leading : .trailing) {
HStack {
Text(message.text)
.padding()
.background(message.received ? Color.gray : Color.blue)
.cornerRadius(30)
}
.frame(maxWidth: 300, alignment: message.received ? .leading : .trailing)
.onTapGesture {
withAnimation {
showTime.toggle()
}
}
if showTime {
Text("\(message.timeStamp.formatted(.dateTime.hour().minute()))")
.font(.caption2)
.foregroundColor(.gray)
.padding(message.received ? .leading : .trailing, 25)
}
}
.frame(maxWidth: .infinity, alignment: message.received ? .leading : .trailing)
.padding(message.received ? .leading : .trailing)
.padding(.horizontal, 4)
}
}
Basically, when the button is pressed, your property messagesDBArray is well and truly append with the new value.
However, and it's really important to understand this point in swiftUI, nothing triggers the refresh of the view.
I suggest you two solutions:
If you don't need messagesDBArray to be outside of ContentView:
You just have to add messagesDBArray as a state in ContentView like following
struct ContentView: View {
#State var messagesDBArray : [SingleMessageBubbleModel] = []
#State private var showOnTheSpotMessaging: Bool = true
#State var textTyped: String = ""
var body: some View {
if (showOnTheSpotMessaging) {
VStack {
HStack {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(messagesDBArray, id: \.id) { message in
MessageBubble(message: message)
}
}
}
.padding(.top, 10)
.background(.gray)
.onChange(of: messagesDBArray.count) { id in
withAnimation {
proxy.scrollTo(id, anchor: .bottom)
}
}
}
.frame( height: 200, alignment: .bottomLeading)
}
HStack () {
TextEditor (text: $textTyped)
.frame(width: 200, height: 200, alignment: .leading)
Button ("Send", action: {
messagesDBArray.append(SingleMessageBubbleModel(text: textTyped, received: true, timeStamp: Date()))
})
}
}
}
}
}
If you need messagesDBArray to be outside of ContentView:
1- Create a class (ViewModel or Service or whatever you wan to call it) with messagesDBArray as a #Published property
final class ViewModel: ObservableObject {
#Published var messagesDBArray : [SingleMessageBubbleModel] = []
}
2- Observe this class in ContentView in order to append and receive the update
struct ContentView: View {
#ObservedObject private var viewModel = ViewModel()
#State private var showOnTheSpotMessaging: Bool = true
#State var textTyped: String = ""
var body: some View {
if (showOnTheSpotMessaging) {
VStack {
HStack {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(viewModel.messagesDBArray, id: \.id) { message in
MessageBubble(message: message)
}
}
}
.padding(.top, 10)
.background(.gray)
.onChange(of: viewModel.messagesDBArray.count) { id in
withAnimation {
proxy.scrollTo(id, anchor: .bottom)
}
}
}
.frame( height: 200, alignment: .bottomLeading)
}
HStack () {
TextEditor (text: $textTyped)
.frame(width: 200, height: 200, alignment: .leading)
Button ("Send", action: {
viewModel.messagesDBArray.append(SingleMessageBubbleModel(text: textTyped, received: true, timeStamp: Date()))
})
}
}
}
}
}
I hope that this is clear to you and that it has been useful 😉

ScrollViewReader is not working and building is pretty slow

I couldn't figure out why it is not working my ScrollViewReader below my code. Please help me figure out the issue. I want to pass my zolyric.number into scrollToIndex and that scrollToIndex will set the number that I want to scroll in my HymnLyrics(). Also, although I couldn't find the error, the building is pretty slow.
Here ZolaiTitles() is the same list just sorting the song titles algebraically and HymnLyrics() is the one to display in the canvas.
struct ZolaiTitles: View {
#AppStorage("scrollToIndex") var scrollToIndex: Int?
#EnvironmentObject var tappingSwitches: TapToggle
let zoLyrics: [Lyric] = LyricList.hymnLa.sorted { lhs, rhs in
return lhs.zoTitle < rhs.zoTitle
}
var body: some View {
ScrollView {
ForEach(zoLyrics, id: \.id) { zoLyric in
VStack {
Button(action: {
//if let lyricNum = String(zoLyric) {
scrollToIndex = zoLyric.number // zolyric.number is already an interger.
//}
self.tappingSwitches.isHymnTapped.toggle()
}, label: {
HStack {
Text(zoLyric.zoTitle)
.foregroundColor(Color("bTextColor"))
.lineLimit(1)
.minimumScaleFactor(0.5)
Spacer()
Text("\(zoLyric.number)")
.foregroundColor(Color("bTextColor"))
}
})
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding([.leading, .bottom, .trailing])
}
}
}
}
struct HymnLyrics: View {
#AppStorage("scrollToIndex") var scrollToIndex: Int = 1
var lyrics: [Lyric] = LyricList.hymnLa
#AppStorage("fontSizeIndex") var fontSizeIndex = Int("Medium") ?? 18
#AppStorage("fontIndex") var fontIndex: String = ""
#AppStorage("showHVNumbers") var showHVNumbers: Bool = true
var body: some View {
ScrollViewReader { proxy in
List(lyrics, id: \.id) { lyric in
VStack(alignment: .center, spacing: 0) {
Text("\(lyric.number)")
.padding()
.multilineTextAlignment(/*#START_MENU_TOKEN#*/.leading/*#END_MENU_TOKEN#*/)
.id(lyric.number)
VStack {
VStack {
Text(lyric.zoTitle)
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.primary)
.autocapitalization(.allCharacters)
Text(lyric.engTitle)
.font(.title3)
.fontWeight(.medium)
.foregroundColor(.secondary)
.italic()
}
// Don't use Padding here!
}
.multilineTextAlignment(.center)
HStack {
Text(lyric.key)
.italic()
Spacer()
Text(lyric.musicStyle)
}
.foregroundColor(.blue)
.padding(.vertical)
}
//.id(lyric.number)
}
.onChange(of: scrollToIndex, perform: { value in
proxy.scrollTo(value, anchor: .top)
})
}
}
}