Expand children of HStack equally? - swiftui

I'm working on converting a little calculator app from UIKit to SwiftUI, because managing the autolayout constraints is getting insane. To that end, I'm trying to make a keypad with various keys grouped in the view hierarchy like this: The top row is an HStack, the number keys are their own view, a VStack of HStack, and then two more VStack (see code below).
Unfortunately, the rows below the top row don’t expand to the full width. I’ve tried a wide variety of .frame(maxWidth: .infinity, maxHeight: .infinity), but none seem to make that lower HStack expand fully horizontally.
I thought it might be because the number keys are their own view, but it does seem to get the heights consistent.
*Bonus points if you can tell me how to expand the "0" key to be twice the width of the "." key in that HStack.
struct
CalculatorView : View
{
var
body: some View
{
VStack(spacing: 0.0)
{
ResultsView()
Keypad()
}
}
}
struct
ResultsView : View
{
var
body: some View
{
ZStack
{
Color.black
}
}
}
struct
Keypad : View
{
var
body: some View
{
ZStack
{
// Color.green
VStack(spacing: 1.0)
{
HStack(spacing: 1.0)
{
Key("AC", action: {})
Key("(", action: {})
Key(")", action: {})
Key("\(Fractional.fractionDash)", action: {})
Key("±", action: {})
}
HStack(spacing: 1.0)
{
Numberpad()
VStack(spacing: 1.0)
{
Key("\(Fractional.fractionSlash)", action: {})
Key("mm", action: {})
Key("\"", action: {})
Key("DEL", action: {})
}
VStack(spacing: 1.0)
{
Key("÷", action: {}, backgroundColor: Color("OperatorButtonBackground"))
Key("×", action: {}, backgroundColor: Color("OperatorButtonBackground"))
Key("-", action: {}, backgroundColor: Color("OperatorButtonBackground"))
Key("+", action: {}, backgroundColor: Color("OperatorButtonBackground"))
}
.foregroundColor(.white)
}
.fixedSize(horizontal: false, vertical: false)
}
.background(Color.blue)
}
.foregroundColor(.black)
.font(Font.system(size: 30.0))
}
}
struct
Numberpad : View
{
var
body: some View
{
ZStack
{
VStack(spacing: 1.0)
{
HStack(spacing: 1.0)
{
Key("7", action: {})
Key("8", action: {})
Key("9", action: {})
}
HStack(spacing: 1.0)
{
Key("4", action: {})
Key("5", action: {})
Key("6", action: {})
}
HStack(spacing: 1.0)
{
Key("1", action: {})
Key("2", action: {})
Key("3", action: {})
}
HStack(spacing: 1.0)
{
Key("0", action: {})
Key(".", action: {})
}
}
}
.background(Color.red)
.foregroundColor(.black)
}
}
struct
Key : View
{
let text : String
let action : () -> ()
let backgroundColor : Color?
init(_ inText: String,
action inAction: #escaping () -> (),
backgroundColor inBC: Color? = nil)
{
self.text = inText
self.action = inAction
self.backgroundColor = inBC
}
var
body: some View
{
ZStack
{
Button(action: {})
{
Text(self.text)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(self.backgroundColor ?? Color.gray)
}
}
.aspectRatio(1.0, contentMode: .fit)
}
}

Approach
.fixedSize(horizontal:, vertical:)
https://developer.apple.com/documentation/swiftui/form/fixedsize(horizontal:vertical:)
Causes the view to grow in the axis marked as false (false means it can grow)
Solution:
Numberpad()
.fixedSize(horizontal: false, vertical: true)

Related

SwiftUI Section view NavigationBar is white using .clipped()?

I am creating a SwiftUI settings view where the user can manage account settings in the app.
The problem is my NavigationBar is white with the current modifiers.
If I don't use the .clipped() modifier then the NavigationBar matches the Section gray background color BUT when scrolling the Settings view up it is shown above the NavigationBar as seen in the first image.
**.clipped() is used to prevent the behavior above BUT then it makes the whole NavigationBar white so it doesn't match the default gray color that it should be.
Also, My grouped section items are not showing the default Divider lines that should be there.
2 Questions
How can I make my Navigation Bar match the gray Section background color.
How can i get the grouped together items in the same section to have Divider lines between them??
struct Settings: View {
var body: some View {
NavigationView {
List {
Section(header:
Text("SUBSCRIPTIONS")
.foregroundColor(.black)) {
SettingsItem(
title: "subscriptionDetails().title",
content: "subscriptionDetails().level",
contentColor: .blue,
action: { print("Action") })
}
Section(header:
Text("ACCOUNT SETTINGS")
.foregroundColor(.black)) {
SettingsItem(title: "Email", action: { print("Action")})
SettingsItem(title: "Gender", action: { print("Action") })
SettingsItem(title: "Phone Number", action: { print("Action") })
})
}
Section(header:
Text("PREFERENCES")
.foregroundColor(.black)) {
SettingsItem(title: "Notifications", action: { print("Action") })
}
Section(header:
Text("SECURITY")
.foregroundColor(.black)) {
SettingsItem(title: "Password", action: { print("Action") })
}
Section(header: Text("ABOUT")
.foregroundColor(.black)) {
SettingsItem(title: "FAQ", action: { print("Action") })
SettingsItem(title: "About", action: { print("Action") })
SettingsItem(title: "Contact", action: { print("Action") })
}
Section(header:
HStack {
Spacer()
Text("Version 1.0.0")
Spacer()
}.foregroundColor(.black)) {
EmptyView()
}
}
.clipped()
.edgesIgnoringSafeArea(.bottom)
.listStyle(.grouped)
.navigationBarTitle("Settings", displayMode: .inline)
.navigationBarBackButtonHidden(true)
.toolbar(content: {
ToolbarItem(placement: .navigationBarLeading) {
DoneButton(action: { print("Done") })
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
print("Logout")
}, label: {
Text("Log Out")
.font(.callout).bold()
.foregroundColor(.red)
})
}
})
}
}
}
struct SettingsItem: View {
var title: String
var content: String?
var contentColor: Color?
let action: () -> Void
var body: some View {
HStack {
Text(title)
.font(.system(size: 18, weight: .regular))
.foregroundColor(.black)
Spacer()
if let content = content, let contentColor = contentColor {
Text(content).padding(.trailing, 10)
.foregroundColor(contentColor)
}
Image(systemName: "chevron.right")
.font(.system(size: 16, weight: .bold))
.foregroundColor(.gray)
}.onTapGesture { print("Action") }
}
}

Need to fix tooltip view position

Here I want to show the tooltip on of tap in the button. The position of the tooltip should be just the top of the TooTipButton and should have the dynamic height to the message text. It is used in many places in 'HStack', 'VStack's with the other content. It should fit in the layout.
For example here the code has HStack inside that Text and TooTipButton. But it does not show the Text component with the text Created by Me
import SwiftUI
import Combine
struct ContentView: View {
#State var showing = false
var body: some View {
GeometryReader { reader in
VStack {
HStack {
Text("Created by Me")
TooTipButton(message: "hey.!! here is some view.", proxy: reader)
}
Spacer()
}
}
}
}
struct ToolTipMessage<Presenting>: View where Presenting: View {
#Binding var isShowing: Bool
let presenting: () -> Presenting
let text: String
let proxy: GeometryProxy
var body: some View {
ZStack(alignment: .center) {
self.presenting()
Group {
Text(text)
.font(.body)
.padding(.horizontal)
.padding(.vertical, 8)
.multilineTextAlignment(.center)
.foregroundColor(.white)
.shadow(radius: 6)
.background(Color(.systemBlue).opacity(0.96) )
.cornerRadius(20)
.transition(.slide)
.opacity(self.isShowing ? 1 : 0)
}.frame(width: proxy.size.width)
}
.onReceive(Just(isShowing)) { shwoing in
if shwoing {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.isShowing = false
}
}
}
.onTapGesture {
self.isShowing = false
}
}
}
extension View {
func toast(isShowing: Binding<Bool>, text: String, proxy: GeometryProxy) -> some View {
ToolTipMessage(isShowing: isShowing,
presenting: { self },
text: text,
proxy: proxy)
}
}
struct TooTipButton: View {
let message: String
#State var showMessage = false
let proxy: GeometryProxy
var body: some View {
Button {
showMessage = true
} label: {
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(.red)
}
.toast(isShowing: $showMessage, text: message, proxy: proxy)
}
}
Here are the images:
Using .overlay and .offset should help:
struct ToolTipMessage<Presenting>: View where Presenting: View {
#Binding var isShowing: Bool
let presenting: () -> Presenting
let text: String
let proxy: GeometryProxy
var body: some View {
ZStack(alignment: .center) {
self.presenting()
.overlay {
Text(text)
.font(.body)
.padding(.horizontal)
.padding(.vertical, 8)
.multilineTextAlignment(.center)
.foregroundColor(.white)
.shadow(radius: 6)
.background(Color(.systemBlue).opacity(0.96) )
.cornerRadius(20)
.transition(.slide)
.opacity(self.isShowing ? 1 : 0)
.frame(width: proxy.size.width)
.offset(x: 0, y: -30)
}
}
.onReceive(Just(isShowing)) { shwoing in
if shwoing {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.isShowing = false
}
}
}
.onTapGesture {
self.isShowing = false
}
}
}

Custom UI Elements not updating in LazyVGrid

I have created a custom element I am calling Slide. I have a LazyVGrid that is displaying my Slide elements. My issue is that when I update the data array that my grid is using, the Slide elements are not updating.
Scenario:
User clicks on the options button on a Slide and changes the Slide color, I am then updating the data array but the Slide element doesn't update despite the data being correct (I have verified this by adding Text(slide.color) into the LazyVGrid which displays the newly set color as expected)
My Suspicions:
I am assuming it doesn't update because I have something wrong in the Slide struct, I am quite new to SwiftUI so I am learning as I go. I must also mention that this loads and displays correctly when I first display the view, the only issue is that it doesn't update when I update the SlideStructure.
Here is the Code:
struct ShowSongFile : View {
#EnvironmentObject var SlideStructure : SlidesModel
#State var selectedSlide : SplaySlide? = nil
var columns = [GridItem(.adaptive(minimum: 320))]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, alignment: .trailing, spacing: 20) {
ForEach(SlideStructure.SongSlides, id:\.id) { slide in
if slide.id == selectedSlide?.id {
Slide(IsSelected:true, SlideData: slide)
} else {
Slide(IsSelected:false, SlideData: slide)
.onTapGesture {
selectSlide(Slide: slide)
}
}
}
}.onAppear(perform: loadSelectedFile)
}
}
Here is the Slide File which also shows how I am setting the colours for the slides:
struct Slide : View {
#State var EnableSlideEditing : Bool? = false
#State var IsSelected : Bool = false
#State var SlideData : SplaySlide
#EnvironmentObject var SlideStructure : SlidesModel
var body : some View {
VStack {
Group{
VStack(alignment: .center) {
let editor = TextEditor(text: $SlideData.lyric)
.multilineTextAlignment(.center)
.padding()
let text = Text(SlideData.lyric)
.multilineTextAlignment(.center)
.frame(width: 320.0, height: 160.0)
if EnableSlideEditing ?? false {
editor
} else {
text
}
Group {
HStack {
Text(SlideData.slideType)
.padding(.leading, 5.0)
Spacer()
MenuButton(label: Image(systemName: "ellipsis.circle")) {
Button("Edit Slide Text", action: {EnableSlideEditing?.toggle()})
Divider()
Menu("Slide Type") {
Button("Verse", action: {SlideType(Type: "Verse", ColorHex: "#f57242")})
Button("Chorus", action: {SlideType(Type: "Chorus", ColorHex: "#0068bd")})
Button("Pre-Chorus", action: {SlideType(Type: "Pre-Chorus", ColorHex: "#02ad96")})
Button("Tag", action: {SlideType(Type: "Tag", ColorHex: "#ad027d")})
Button("Bridge", action: {SlideType(Type: "Bridge", ColorHex: "#02ad96")})
}
Menu("Transitions") {
Button("Option 1", action: {})
Button("Option 2", action: {})
}
Divider()
Button("Delete Slide", action: {})
Button("Duplicate Slide", action: {})
}
.menuButtonStyle(BorderlessButtonMenuButtonStyle())
.frame(alignment: .trailing)
.padding(.trailing, 5.0)
.buttonStyle(PlainButtonStyle())
}
}
.frame(width: 320, height: 20, alignment: .leading)
.background(Color.init(hex: SlideData.slideBorderColorHex))
}
}
.frame(width: 320, height: 180, alignment: .bottomLeading)
.background(IsSelected ? Color.accentColor : .black)
.cornerRadius(10)
}
}
func SlideType(Type:String, ColorHex: String) {
for (index, slide) in SlideStructure.SongSlides.enumerated() {
if slide.id == self.SlideData.id {
SlideStructure.SongSlides[index].slideBorderColorHex = ColorHex
SlideStructure.SongSlides[index].slideType = Type
ShowSongFile.main?.SongFile.slides = SlideStructure.SongSlides
ShowSongFile.main!.SongFile.SaveSongToDisk()
}
}
}
}
SlideStructure: (SlideModel)
class SlidesModel : ObservableObject {
#Published var SongSlides : [SplaySlide] = []
}
There are too many missing parts to be able to test any particular solution, so I will take a guess.
In ShowSongFile you could try :
LazyVGrid(columns: columns, alignment: .trailing, spacing: 20) {
ForEach(SlideStructure.SongSlides, id:\.id) { slide in
Slide(SlideData: slide) // <--- here
.onTapGesture {
selectSlide(Slide: slide)
}
.background(slide.id == selectedSlide?.id ? Color.accentColor : .black) // <--- here
}
and adjust Slide accordingly, that is, remove IsSelected and .background(IsSelected ? Color.accentColor : .black).
P.S: your naming and case of your variables and functions makes
reading your code less than appealing.

Result of 'View' initializer is unused

My Custom button does not tap and passes to next view called AddCreditCardView.
I have tested the button action with print statement and it won't work too.
I copied my code below in separate.
This is my ContentView
import SwiftUI
struct ContentView: View {
let membershipRows = MembershipData.listData()
let corporateRows = CorporateData.listData()
let otherOperationRows = OtherOperationsData.listData()
#State var selectedCard = CreditCard(id: "", cardOwnerName: "", cardNumber: "", cardExpMonth: "", cardExpYear: "", ccv: "")
#State var shown: Bool = false
var body: some View {
NavigationView {
VStack {
List {
Section(header: Text("Bireysel")) {
ForEach(membershipRows) { row in
NavigationLink(destination: CreditCardView()) {
RowElementView(row: row)
}
}
}
if self.corporateRows.count == 0
{
Rectangle()
.background(Color(.white))
.edgesIgnoringSafeArea(.all)
.foregroundColor(.white)
.padding(.vertical,32)
}
else {
Section(header: Text("Kurumsal")) {
ForEach(corporateRows) { row in
RowElementView(row: row)
}
}
}
Section(header: Text("Diger Islemler")) {
ForEach(otherOperationRows) { row in
RowElementView(row: row)
}
}
Rectangle()
.foregroundColor(.clear)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height )
}
.navigationBarTitle("Odeme Yontemleri", displayMode: .inline)
.font(Font.custom("SFCompactDisplay", size: 16))
Button(action: {
AddCreditCardView(item: self.selectedCard)
}, label: { CustomButton(title: "Odeme Yontemi Ekle", icon: .none, status: .enable)
})
}
}
}
This is my AddCreditCardView
import SwiftUI
struct AddCreditCardView: View {
var item: CreditCard
var body: some View {
NavigationView {
VStack {
TopBar()
Spacer()
CardInfo()
Spacer()
}
.navigationBarTitle("Odeme Yontemi", displayMode: .inline)
}
}
}
struct TopBar : View {
var body: some View {
VStack {
HStack() {
Image("addcreditcard")
Image("line")
Image("locationBar")
Image("line")
Image("check-circle")
}
.padding(.horizontal,62)
VStack {
Text("Kredi Karti Ekle")
.font(Font.custom("SFCompactDisplay-Bold", size: 14))
Text("1. Adim")
.font(Font.custom("SFCompactDisplay", size: 14))
.fontWeight(.regular)
.foregroundColor(.gray)
}
}
.padding()
}
}
struct CardInfo : View {
var body: some View {
VStack {
CustomTextField(tFtext: "Kartin Uzerindeki Isim", tFImage: "user")
.textContentType(.givenName)
CustomTextField(tFtext: "Kredi Kart Numarasi", tFImage: "credit")
.textContentType(.oneTimeCode)
.keyboardType(.numberPad)
HStack {
CreditCardDateTextField(tFtext: "", tFImage: "date")
.textContentType(.creditCardNumber)
Spacer()
Text("|")
.foregroundColor(.black)
.overlay(
Rectangle()
.frame(width: 60, height: 53))
CustomTextField(tFtext: "CCV", tFImage: "")
.textContentType(.creditCardNumber)
}
.foregroundColor(Color(#colorLiteral(red: 0.9647058824, green: 0.9725490196, blue: 0.9882352941, alpha: 1)))
CustomTextField(tFtext: "Kart Ismi", tFImage: "cardEdit")
Spacer()
}
}
}
And Finally, this is my CreditCard Model
import SwiftUI
struct CreditCard: Identifiable {
var id: String = UUID().uuidString
var cardOwnerName : String
var cardNumber: String
var cardExpMonth: String
var cardExpYear: String
var ccv: String
Seems like you are trying to navigate to AddCreditCardView on the button press. The action closure can not present a view automatically like that! You should change that code to something like this:
#State var navigated = false
,,,
NavigationLink("AddCreditCardView", destination: AddCreditCardView(), isActive: $navigated)
Button(action: { self.navigated.toggle() },
label: { CustomButton(title: "Odeme Yontemi Ekle", icon: .none, status: .enable) })
changing the navigated state will show the next page as it seems you wished.

Button is not selectable on tvOS using own ButtonStyle in SwiftUI

After replacing a standard button style with a custom one, the button isn't selectable anymore on tvOS (it works as expected on iOS). Is there a special modifier in PlainButtonStyle() that I'm missing? Or is it a bug in SwiftUI?
Here's the snipped that works:
Button(
action: { },
label: { Text("Start") }
).buttonStyle(PlainButtonStyle())
and here's the one that doesn't:
Button(
action: { },
label: { Text("Start") }
).buttonStyle(RoundedButtonStyle())
where RoundedButtonStyle() is defined as:
struct RoundedButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding(6)
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(100)
}
}
If you create your own style you have to handle focus manual. Of course there are different ways how you could do this.
struct RoundedButtonStyle: ButtonStyle {
let focused: Bool
func makeBody(configuration: Configuration) -> some View {
configuration
.label
.padding(6)
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(100)
.shadow(color: .black, radius: self.focused ? 20 : 0, x: 0, y: 0) // 0
}
}
struct ContentView: View {
#State private var buttonFocus: Bool = false
var body: some View {
VStack {
Text("Hello World")
Button(
action: { },
label: { Text("Start") }
).buttonStyle(RoundedButtonStyle(focused: buttonFocus))
.focusable(true) { (value) in
self.buttonFocus = value
}
}
}
}
I have done it in this way and it's working fine, what you need to do is just handling the focused state.
struct AppButtonStyle: ButtonStyle {
let color: Color = .clear
func makeBody(configuration: Configuration) -> some View {
return AppButton(configuration: configuration, color: color)
}
struct AppButton: View {
#State var focused: Bool = false
let configuration: ButtonStyle.Configuration
let color: Color
var body: some View {
configuration.label
.foregroundColor(.white)
.background(RoundedRectangle(cornerRadius: 20).fill(color))
.compositingGroup()
.shadow(color: .black, radius: 5)
.scaleEffect(focused ? 1.1 : 1.0)
.padding()
.focusable(true) { focused in
withAnimation {
self.focused = focused
}
}
}
}
}
When the button is getting focused I'm just scaling it and you can do something else as you wish with the same idea, so let's say you wan't to change the background color:
.background(RoundedRectangle(cornerRadius: 20).fill(focused ? .red : .white))
To use:
struct SomeView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: Text("Destination")) {
Text("Hi")
}
.buttonStyle(AppButtonStyle())
.padding()
}
}
}