Change GroupBox background color in SwiftUI - swiftui

How can I change the default gray background color of a GroupBox view in SwiftUI?
I tried adding a background modifier, but this just changes the white background underneath the box (see screenshot).
GroupBox(label: Text("Label"), content: {
Text("Content")
})
.background(Color.blue)

This is default group box style. You can create whatever group box needed using custom style.
Here is an example. Tested with Xcode 12 / iOS 14.
struct DemoGroupBox: View {
var body: some View {
GroupBox(label: Text("Label"), content: {
Text("Content")
})
.groupBoxStyle(TransparentGroupBox())
.padding()
}
}
struct TransparentGroupBox: GroupBoxStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.content
.frame(maxWidth: .infinity)
.padding()
.background(RoundedRectangle(cornerRadius: 8).fill(Color.blue))
.overlay(configuration.label.padding(.leading, 4), alignment: .topLeading)
}
}

This problem can be solved in this way
Tested on iOS 16, Xcode 14.0 Beta
struct TestView: View {
var body: some View {
GroupBox(label: Text("Label"), content: {
Text("Content")
})
.groupBoxStyle(ColoredGroupBox())
.padding()
}
}
struct ColoredGroupBox: GroupBoxStyle {
func makeBody(configuration: Configuration) -> some View {
VStack {
HStack {
configuration.label
.font(.headline)
Spacer()
}
configuration.content
}
.padding()
.background(RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(.blue)) // Set your color here!!
}
}
Result:
Full code for above result
struct TestView: View {
var body: some View {
VStack {
Divider()
HStack {
Text("Asperi's solution")
Text("(Problematic with label)")
.bold()
}
GroupBox(label: Text("Label"), content: {
Text("Content")
})
.groupBoxStyle(TransparentGroupBox())
.padding()
Divider()
Text("My Solution")
GroupBox(label: Text("Label"), content: {
Text("Content")
})
.groupBoxStyle(ColoredGroupBox())
.padding()
Divider()
Text("Default")
GroupBox(label: Text("Label"), content: {
Text("Content")
})
.padding()
Divider()
}
}
}
struct TransparentGroupBox: GroupBoxStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.content
.frame(maxWidth: .infinity)
.padding()
.background(RoundedRectangle(cornerRadius: 8).fill(Color.blue))
.overlay(configuration.label.padding(.leading, 4), alignment: .topLeading)
}
}
struct ColoredGroupBox: GroupBoxStyle {
func makeBody(configuration: Configuration) -> some View {
VStack {
HStack {
configuration.label
.font(.headline)
Spacer()
}
configuration.content
}
.padding()
.background(RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(.blue)) // Set your color here!!
}
}

Short answer: it is currently not properly supported by SwiftUI :(
Longer answer: you can create a custom GroupBoxStyle (see example) which will allow you to change the background... but at the cost of having to then manually layout the contents (which somewhat defeats the purpose).

Related

move content by scroll when overlay is presented

I created some MRE:
struct ContentView: View {
#State private var presentOverlay: Bool = false
var body: some View {
GeometryReader { geo in
List {
Section {
VStack(alignment: .center) {
Spacer()
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
.onTapGesture {
presentOverlay.toggle()
}
Text("Hello, world!")
}
.frame(height: geo.size.height * 0.8)
} header: {
Text("Header")
} footer: {
Text("Footer")
}
}
.overlay(alignment: .bottom) {
if presentOverlay {
Text("Some overlay")
.frame(width: geo.size.width, height: geo.size.height * 0.2)
.background(.yellow)
.transition(.move(edge: .bottom))
}
}
}
.animation(.default, value: presentOverlay)
}
}
Goal is to, when overlay appears, move content of list, so Image can be presented and clickable (currently overlay is going above it).
Is it possible to move content like it is scrolled using List component?

SwiftUI GroupBoxStyle fill height

I have a custom GroupBoxStyle where I would like to add a think coloured line to its left edge that takes the entire height of the GroupedBox. This GroupedBox is then used in rows in a VStack
Here in blue is the content of the GroupBox, in yellow is that line I want to add. The blue content can be any view.
In my my current implementation (see below) but the problem is that the vertical line does not take all the height of the GroupBox
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 8) {
GroupBox {
HStack {
VStack {
Text("Hello")
Text("Hello")
Text("Hello")
Text("Hello")
}
Spacer()
}
}
GroupBox {
Color.blue
.frame(height: 50)
}
}
// try to uncomment this
// .padding(8)
}
.groupBoxStyle(StandardGroupBoxStyle())
.navigationBarTitle("My Group", displayMode: .large)
}
}
}
public struct StandardGroupBoxStyle: GroupBoxStyle {
public func makeBody(configuration: Self.Configuration) -> some View {
HStack(spacing: 0) {
Color.yellow
.frame(width: 5)
.frame(maxHeight: .infinity)
VStack {
configuration
.content
}
.border(Color.green)
Spacer()
}
.border(Color.red)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This implementation and example creates this (note the yellow not taking the entire height of the row.
Here is a solution (I kept original extra modifiers, but assume they are for debug purpose). Tested with Xcode 12.1 / iOS 14.1
public struct StandardGroupBoxStyle: GroupBoxStyle {
public func makeBody(configuration: Self.Configuration) -> some View {
HStack(spacing: 0) {
configuration.content
.padding(.leading, 5)
.border(Color.green) // << debug?
Spacer() // << debug?
}
.overlay(Color.yellow.frame(width: 5), alignment: .leading)
.border(Color.red) // << debug?
}
}

How Can I Handle Tap Gesture on Widget?

I'm writing a widget with WidgetKit and I want to make the widget's content is clickable. For example, if users click to standings, I want to open the standings tab when the app becomes active.
I tried to use notification between the app and widget but the tap gesture is not working, I added print inside of the tap gesture but it did not appear in the console. Also, I added the same app group to both of them.
WidgetView:
struct LargeWidget : View {
#State var standings : [StandingsTable]
var body: some View {
VStack(alignment:.leading){
if standings.count > 0{
HStack(spacing:5){
Text("#").foregroundColor(.gray)
.frame(width: 30)
Text("Team".localized).foregroundColor(.gray)
Spacer()
Text("_D".localized).foregroundColor(.gray)
.frame(width: 30)
Text("_L".localized).foregroundColor(.gray)
.frame(width: 30)
Text("_W".localized).foregroundColor(.gray)
.frame(width: 30)
Text("_P".localized).foregroundColor(.gray)
.frame(width: 30)
}
Divider()
ForEach(0..<5, id: \.self) { i in
HStack(spacing:5){
Text(standings[i].rank)
.font(.system(size: 15))
.padding(.vertical, 3)
.frame(width: 30)
.background(Color(UIColor.systemBackground))
.cornerRadius(4)
Text(standings[i].name)
.lineLimit(1)
.padding(.leading, 5)
Spacer()
Text(standings[i].drawn)
.frame(width: 30)
Text(standings[i].lost)
.frame(width: 30)
Text(standings[i].won)
.frame(width: 30)
Text(standings[i].points)
.frame(width: 30)
}
.padding(.vertical, 5)
.background(standings[i].name == "Besiktas" ? Color(UIColor.systemGray6) : Color.clear)
.cornerRadius(8)
}
Spacer(minLength: 0)
}else{
Text("Large")
.padding()
}
}.padding()
.onTapGesture {
print("clicked to standings")
DispatchQueue.main.async(execute: {
NotificationCenter.default.post(name: NSNotification.Name("standings"), object: nil, userInfo: nil)
})
}
}
}
and here ContentView in app:
import SwiftUI
extension NSNotification {
static let openStandings = NSNotification.Name.init("standings")
}
struct ContentView: View {
#State var show: Bool = false
var body: some View {
NavigationView{
Text("Hello, world!")
.padding()
}.sheet(isPresented: self.$show) {
VStack{
Text("Notification")
.padding()
}
}
.onReceive(NotificationCenter.default.publisher(for: NSNotification.openStandings))
{ obj in
self.show.toggle()
}
}
}
Screenshot of Widget
Okay I created a new project with SwiftUI Environments (not old way AppDelegate and SceneDelegate).
Then I use Link for tap actions and I got it with .onOpenURL modifier in ContentView. It works :)
ContentView:
import SwiftUI
enum SelectedTab: Hashable {
case home
case standings
case options
}
struct ContentView: View {
#State var selectedTab: SelectedTab = .home
var body: some View {
TabView(selection: self.$selectedTab) {
Text("Home")
.tabItem {
Image(systemName: "house")
.renderingMode(.template)
Text("Home")
}.tag(SelectedTab.home)
Text("Standings")
.tabItem {
Image(systemName: "list.number")
.renderingMode(.template)
Text("Standings")
}.tag(SelectedTab.standings)
Text("Options")
.tabItem {
Image(systemName: "gear")
.renderingMode(.template)
Text("Options")
}.tag(SelectedTab.options)
.onOpenURL { (url) in
if url.absoluteString == "widget-deeplink://standings"{
self.selectedTab = .standings
}
}
}
}
}
Link usage example in Widget:
Link(destination: URL(string: "widget-deeplink://standings")!) {
Text("Link Test")
}

SwiftUI Question: Place text underneath(outside) of list view

I am trying to place this information underneath my list. I don't want it to be included in the list but rather it should be displayed on the view background. Sort of like the old settings in iOS 6. I've tried placing my code snippets in different spots and removing stacks snd even changing what stacks I'm using and I cant figure it out. Heres my code and I attached a screenshot https://i.stack.imgur.com/PcUgJ.png for reference. Thank you to anyone who can help my noob self.
My Code:
import SwiftUI
struct SettingsAboutView: View {
var body: some View {
List{
//Top Section
Section {
HStack(spacing: 30) {
Spacer()
ZStack {
Rectangle()
.frame(width: 50, height: 50)
.clipShape(Rectangle())
.shadow(radius: 2)
.foregroundColor(Color("PineGlade"))
Image(systemName: "leaf.arrow.circlepath")
.resizable()
.frame(width: 25, height: 25)
.clipShape(Rectangle())
.foregroundColor(.white)
}.cornerRadius(10)
VStack(alignment: .leading) {
Text("Gardn")
.font(.system(size: 32))
.fontWeight(.bold)
.foregroundColor(Color("ShipsOfficer"))
Text("OnlyOdds.co Labs")
.font(.system(size: 13))
.fontWeight(.medium)
.foregroundColor(Color("ShipsOfficer"))
}
Spacer()
}.padding()
NavigationLink(destination: SettingsPrivacyView()) {
Button(action: {
print("Privacy Settings")
}) {
SettingsCell(title: "Privacy", imgName: "eye.slash", clr: Color("PineGlade"))
}
}
NavigationLink(destination: SettingsNotificationsView()) {
Button(action: {
print("Notification Settings")
}) {
SettingsCell(title: "Notifications", imgName: "bell", clr: Color("PineGlade"))
}
}
}
//Technical Info
HStack(spacing: 62) {
Spacer()
Text("V \(UIApplication.appVersion!)")
.font(.system(size: 14))
.fontWeight(.light)
.foregroundColor(Color("ShipsOfficer"))
.opacity(0.5)
Text("Build \(UIApplication.appBuild!)")
.font(.system(size: 14))
.fontWeight(.light)
.foregroundColor(Color("ShipsOfficer"))
.opacity(0.5)
Spacer()
}
}.listStyle(GroupedListStyle())
.environment(\.horizontalSizeClass, .regular)
.navigationBarTitle("Settings", displayMode: .inline)
}
}
struct SettingsAboutView_Previews: PreviewProvider {
static var previews: some View {
SettingsAboutView()
}
}
extension UIApplication {
static var appVersion: String? {
return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
}
}
extension UIApplication {
static var appBuild: String? {
return Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String
}
}
What you are looking for is a Footer. The Section View takes Footer as ViewBuilder as argument.
Section(footer: Text("Your footer text")) {
//Your Content here
}
You can pass Text() or create your own custom view.
Just put the List in a VStack and put
Text(yourtext)
Under the list

SwiftUI how to set image for NavigationBar titleView like in UIKit?

I want to set an image in the titleView of NavigationBar in SwiftUI, as we do in UIKit
navigationItem.titleView = UIImageView(image: UIImage(named: "logo"))
this is how we do it in UIKit.
anyone know how to do it?
Here's how to do it:
Add SwiftUIX to your project.
Set your custom title view via View.navigationBarTitleView(_:displayMode:)
Example code:
struct ContentView: View {
public var body: some View {
NavigationView {
Text("Hello World")
.navigationBarTitleView(MyView())
}
}
}
Simple, Just add your root view into ZStack with top alignment and add your custom center view after root view
struct CenterNavigattionBar: View {
var body: some View {
ZStack(alignment: .top){
//Root view with empty Title
NavigationView {
Text("Test Navigation")
.navigationBarTitle("",displayMode: .inline)
.navigationBarItems(leading: Text("Cancle"), trailing: Text("Done"))
}
//Your Custom Title
VStack{
Text("add title and")
.font(.headline)
Text("subtitle here")
.font(.subheadline)
}
}
}
}
Before Image
After Image
Just use a toolbar.
You can add any views
import SwiftUI
struct HomeView: View {
// MARK: - Initializer
init() {
let appearance = UINavigationBar.appearance()
appearance.isOpaque = true
appearance.isTranslucent = false
appearance.barTintColor = UIColor(named: "background")
appearance.shadowImage = UIImage()
}
// MARK: - View
// MARK: Public
var body: some View {
NavigationView {
VStack(spacing: 20) {
Text("Hello")
Text("Navigation Bar Test")
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(leading: leadingBarButtonItems, trailing: trailingBarButtonItems)
.toolbar {
ToolbarItem(placement: .principal) {
VStack {
Text("Title").font(.headline)
Text("Subtitle").font(.subheadline)
}
}
}
}
}
// MARK: Private
private var leadingBarButtonItems: some View {
Button(action: {
}) {
Text("Left Button")
.font(.system(size: 12, weight: .medium))
}
}
private var trailingBarButtonItems: some View {
HStack {
Button(action: {
}) {
Text("R1\nButton")
.font(.system(size: 12, weight: .medium))
.multilineTextAlignment(.center)
}
Button(action: {
}) {
Text("R2\nButton")
.font(.system(size: 12, weight: .medium))
.multilineTextAlignment(.center)
}
}
}
}
Currently, you can't.
There are two overloads for .navigationBarTitle(), taking either a Text view or a type conforming to StringProtocol. You can't even pass in a modified view like Text("Title").font(.body). This would be a great feature, I'd submit a feature request: http://feedbackassistant.apple.com
Maybe this works for you?
Basically:
Use GeometryReader to get the width of the screen
Have NavigationBarItems(leading: HStack {Spacer() Image("name").resizable().frame(width:..., height: ..., alignment: .center Spacer()}.frame(width:geometry.size.width)
Example code:
struct ContentView: View {
var body: some View {
NavigationView {
GeometryReader { geometry in
Text("Hello, world!")
.padding()
.navigationTitle("test")
.navigationBarItems(leading: HStack {
Spacer()
Image("money")
.resizable()
.frame(width: 50, height: 50, alignment: .center)
Spacer()
}
.frame(width: geometry.size.width)
)
}
}
}
}
Try this...
How to put a logo in NavigationView in swiftui?
This shows how to handle adding an Image to NavigationView in SwiftUI. Hope it helps.