SwiftUI add border to onSelect horizontal Scrollview image - swiftui

I tried to make a horizontal scrollview that contained a list of item. when I clicked any of the item, it will show a border(highlight) behind the item. When I press another item, the previous border disappear and the latest clicked item appear the border. It just looks like a selection horizontal scrollview. I have no idea about how to do so.
ScrollView(.horizontal, showsIndicators: false){
LazyHStack{
ForEach(self.staffs.indices, id: \.self){ staff in
VStack{
Image(staff.image)
.resizable()
.frame(width: 100, height: 100)
.clipShape(Circle())
}
.onTapGesture {
print(createBookingJSON.staffs[staff].staffID!)
}
}
}
}

Convert your VStack into a standalone view, then pass a Binding to it that can be read from the Parent View. Your new VStack standalone view needs an OnTapGesture or an Action through a button to toggle it's state. We will make your ForEach a "single selection" list as you request.
NEW View to use inside your ForEach:
struct ItemCell: View {
var item: Item
#Binding var selectedItem: Item?
var body: some View {
VStack{
Image(item.image)
.resizable()
.frame(width: 100, height: 100)
.border(Color.green, width: (item == selectedItem) ? 20 : 0)
}
.onTapGesture {
self.selectedItem = item
print(createBookingJSON.staffs[staff].staffID!)
}
}
}
Now in the view that contains your Foreach, add a State Var of selectedItem so you can read the Binding you created in your cells. And Replace your VStack with your new ItemCell:
struct YourParentView: View {
#State var selectedItem: Item? = nil
var body: some View {
ScrollView(.horizontal, showsIndicators: false){
LazyHStack{
ForEach(self.staffs.indices, id: \.self){ staff in
ItemCell(item: staff, selectedItem: self.$selectedItem)
}
}
}
}
}
Now when you click on the item, a border should appear. You may need to play with the border depending on your design, and the clipShape you have used of Circle(). Good Luck comrade.

Related

How do you keep 'fullScreenISPresented' from dismissing itself when the screen rotates?

I have a LazyVGrid with a layout count: 2 when in portrait, and court: 3 when in landscape, in a scrollview. I use a ternary to change the count. Problem is when I scroll down than select a cell, when the model slides up and I rotate, the view dismisses by itself. I also notice the scroll seems to be in a totally different location.
Do I need to build this differently? Funny thing is it only happens at certain places down in the scrollview. Its not consistent. Sometimes it works fine then as I continue to scroll down it'll start to happen.
If I don't change the layout count in portrait or landscape, it works fine. It seems change the count causes this.
struct Feed_View: View {
#EnvironmentObject var viewModel : Post_View_Model
#Environment(\.verticalSizeClass) var sizeClass
#FocusState private var isFocused: Bool
var body: some View {
Color("BGColor").ignoresSafeArea()
ZStack {
VStack (alignment: .center, spacing: 0) {
//MARK: - NAVIGATION BAR
NavBar_View() // Top Navigation bar
.frame(maxHeight: 40, alignment: .center)
//MARK: - SCROLL VIEW
ScrollView (.vertical, showsIndicators: false) {
//MARK: - FEED FILL
let layout = Array(repeating: GridItem(.flexible(), spacing: 10), count: sizeClass == .compact ? 3 : 2)
LazyVGrid(columns: layout, spacing: 10) {
ForEach (viewModel.posts, id: \.self) { posts in
Feed_Cell(postModel: posts)
} //LOOP
} //LAZYV
.padding(.horizontal, 10).padding(.vertical, 10)
} //SCROLL
} //V
} //Z
}
}

Navigationlink question in Xcode 13.1 -- Blanks instead of links?

Sorry for the newbie question, but I'm stuck on why Navigationlink produces no links at all. Xcode compiles, but there's a blank for where the links to the new views are. This particular view is View 3 from ContentView, so the structure is ContentView -> View 2 -> View 3 (trying to link to View 4).
struct MidnightView: View {
var hourItem: HoursItems
#State var showPreferencesView = false
#State var chosenVersion: Int = 0
#State var isPsalmsExpanded: Bool = false
#State var showLXX: Bool = false
var body: some View {
ScrollView (.vertical) {
VStack (alignment: .center) {
Group {
Text (hourItem.hourDescription)
.font(.headline)
Text ("Introduction to the \(hourItem.hourName)")
.font(.headline)
.bold()
.frame(maxWidth: .infinity, alignment: .center)
ForEach (tenthenou, id: \.self) {
Text ("\($0)")
Text ("\(doxasi)")
.italic()
}
}
.padding()
NavigationView {
List {
ForEach (midnightHours, id:\.id) {watch in
NavigationLink ("The \(watch.watchName)", destination: MidnightWatchView (midnightItem: watch, chosenVersion: self.chosenVersion, isPsalmsExpanded: self.isPsalmsExpanded, showLXX: self.showLXX))
}
}
}
Group {
Text ("Absolution of the \(hourItem.hourName)")
.font(.headline)
Text (absolutionTexts[(hourItem.hourName)] ?? " ")
Divider()
Text ("Conclusion of Every Hour")
.font(.headline)
Text (hourConclusion)
Divider()
Text (ourFather)
}
.padding()
}
}
.navigationBarTitle ("The Midnight Hour", displayMode: .automatic)
.navigationBarItems (trailing: Button (action: {self.showPreferencesView.toggle()}) {Text (psalmVersions[chosenVersion])}
.sheet(isPresented: $showPreferencesView) {PreferencesView(showPreferencesView: self.$showPreferencesView, chosenVersion: self.$chosenVersion, isPsalmsExpanded: self.$isPsalmsExpanded, showLXX: self.$showLXX)})
}
}
The cause of the NavigationLink not appearing is probably due to the fact that you're including a List within a ScrollView. Because List is a scrolling component as well, the sizing gets messed up and the List ends up with a height of 0. Remove the List { and corresponding } and your links should appear.
There are a number of other potential issues in your code (as I alluded to in my comment), including a NavigationView in the middle of a ScrollView. I'd remove that as well, as you probably have a NavigationView higher up in your view hierarchy already.

Radiobuttons in List swiftUI

Im displaying data from server and need to display the options in radiobuttons. But default none of the button should be selected. I was able to display radiobuttons. But when a particular button is clicked, only its image should be changed. Though with my code all the other button images too change. I have gone through some references SwiftUI - How to change the button's image on click?, but couldn't get it work. As am a newbie to SwiftUI, strucked here.
struct ListView: View {
#State var imageName: String = "radio-off"
var body: some View {
List(vwModel.OpnChoice.ItemList) { opn in
VStack(alignment:.leading) {
ForEach(opn.Choices.indices, id: \.self) { row in
Button(action: {
print("\(opn.Choices[row].ChoiceId)")
self.imageName = "radio-On"
}) {
Image(imageName)
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 30, height: 30)
}
Text(opn.Choices[row].ChoiceName)
}
}
}
}
}
As you are working with indices anyway, add a #Published property to your view model which contains the selected index.
Then set the index in the button action. The redraw of the view sets the button at the selected index to the on-state and the others to the off-state.
As I don't know your environment this is a simplified stand-alone view model and view with SF Symbols images
class VMModel : ObservableObject {
#Published var selectedOption = -1
var numberOfOptions = 5
}
struct ListView: View {
#StateObject private var vwModel = VMModel()
var body: some View {
VStack(alignment:.leading) {
ForEach(0..<vwModel.numberOfOptions, id: \.self) { opn in
Button(action: {
vwModel.selectedOption = opn
print("index \(opn) selected")
}) {
Image(systemName: opn == vwModel.selectedOption ? "largecircle.fill.circle" : "circle")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 30, height: 30)
}
}
}
}
}
Update:
Meanwhile you can use a Picker with the .radioGroup modifier
enum Choice {
case one, two, three
}
struct ListView: View {
#State private var choice : Choice = .one
var body: some View {
Picker(selection: $choice, label: Text("Select an option:")) {
Text("One").tag(Choice.one)
Text("Two").tag(Choice.two)
Text("Three").tag(Choice.three)
}.pickerStyle(.radioGroup)
}
}
At the moment you are storing a single #State property "imageName" for the entire list. And then when any of the buttons in the list are tapped you are changing that single property. Which will affect all the buttons.
I'd suggest removing this property and putting something into the viewModel.
You appear to have a view model with an array called vwModel.opnChoice.itemList.
There are multiple ways of making this work. You could stop a boolean in the itemList to say whether each item is selected or not. But, as you want it to work like radio buttons what might be better is to have a property on the vwModel like selectedItem.
The button could then do...
vwModel.selectedItemId = opn.choices[row].choiceId
Then you button Label would be...
Image(vsModel.selectedItemId == opn.choices[row].choiceId ? "radio-on" : "radio-off")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 30, height: 30)
This would allow you to toggle the button when it is tapped and should turn the other buttons off.

SwiftUI: Spacing between List Items

I have some code that displays a list with a title and a rectangle and if the user taps on it, it navigates to a detail page.
But my code seems to have multiple problems:
The spacer is not working. I want the name on the left and the rectangle on the right like a spacer in a Stack would push it.
The background color of the rectangle is not applied. It is just black
If I add a navigationTitle, I see constraint errors (see below)
(
"<NSLayoutConstraint:0x600002438f50 'BIB_Trailing_CB_Leading' H:[_UIModernBarButton:0x7fd15fe088b0]-(6)-[_UIModernBarButton:0x7fd15fe064d0'World Clock'] (active)>",
"<NSLayoutConstraint:0x600002438fa0 'CB_Trailing_Trailing' _UIModernBarButton:0x7fd15fe064d0'World Clock'.trailing <= _UIButtonBarButton:0x7fd15fe05310.trailing (active)>",
"<NSLayoutConstraint:0x600002439d10 'UINav_static_button_horiz_position' _UIModernBarButton:0x7fd15fe088b0.leading == UILayoutGuide:0x600003e04a80'UIViewLayoutMarginsGuide'.leading (active)>",
"<NSLayoutConstraint:0x600002439d60 'UINavItemContentGuide-leading' H:[_UIButtonBarButton:0x7fd15fe05310]-(0)-[UILayoutGuide:0x600003e049a0'UINavigationBarItemContentLayoutGuide'] (active)>",
"<NSLayoutConstraint:0x6000024350e0 'UINavItemContentGuide-trailing' UILayoutGuide:0x600003e049a0'UINavigationBarItemContentLayoutGuide'.trailing == _UINavigationBarContentView:0x7fd15fd0a640.trailing (active)>",
"<NSLayoutConstraint:0x60000243a4e0 'UIView-Encapsulated-Layout-Width' _UINavigationBarContentView:0x7fd15fd0a640.width == 0 (active)>",
"<NSLayoutConstraint:0x6000024354a0 'UIView-leftMargin-guide-constraint' H:|-(0)-UILayoutGuide:0x600003e04a80'UIViewLayoutMarginsGuide' (active, names: '|':_UINavigationBarContentView:0x7fd15fd0a640 )>"
)
Here is my code:
var body: some View {
NavigationView {
List {
ForEach(viewModel.cityList, id: \.name) { city in
NavigationLink(
destination: CityDetailView(city: city)){
HStack{
Text(city.name)
Spacer()
CustomView()
}
}.frame(height: 100)
}
}
.navigationTitle("World Clock")
}
}
CustomView:
var body: some View {
GeometryReader { geo in
Circle().frame(width: geo.size.height, height: geo.size.height)
.background(Color.green)
}
}
Any idea what is wrong?
Your spacer doesn't work because there's NavigationLink in the HStack which took all the space: it has bigger priority than Spacer.
If you wanna use NavigationLink with EmptyView, it should be placed in a ZStack with nearby view, but in this case you can simply place content of your cell inside NavigationLink label.
Rectangle is not a view but a Shape, and as with any other Shape its color should be set with foregroundColor, not with background.
GeometryReader always takes all available space, unless you're adding size modifiers to it. It cannot wrap around content by itself. So you can use following trick: add onAppear for an item inside GeometryReader and pass width to state variable, which will then add width modifier to GeometryReader.
struct ContentView: View {
#ObservedObject var viewModel = CityListViewModel()
var body: some View {
NavigationView {
List {
ForEach(viewModel.cityList, id: \.name) { city in
NavigationLink(
destination: Text(city.name)){
HStack{
Text(city.name)
Spacer()
CustomView()
}
}.frame(height: 100)
}
}
.navigationTitle("World Clock")
}
}
}
struct CustomView: View {
#State
var width: CGFloat?
var body: some View {
GeometryReader { geo in
Circle().frame(width: geo.size.height, height: geo.size.height)
.background(Color.green)
.onAppear {
width = geo.size.height
}
}.frame(width: width)
}
}
If you see some NSLayoutConstraint broken which you didn't create, like when using SwiftUI, you can ignore it: it's not your bug.

SwiftUI - switch toolbar states

Goal: a toolbar that switches states, according to object selected (image, text, shape)
What I did:
iimport SwiftUI
struct ToolbarMaster : View {
#State var showtoolbar = false
var toolbarmaster: [ToolbarBezier] = []
var body: some View {
HStack {
Spacer()
VStack {
Button(action: {self.showtoolbar.toggle() }) {
Image(systemName: "gear")
}
.padding(.leading)
Image("dog")
Text("Im a text")
.font(.largeTitle)
.color(.black)
Path(ellipseIn: CGRect(x: 0, y: 0, width: 100, height: 100))
.fill(Color.black)
}
NavigationView {
ZStack {
ToolbarBezier()
ToolbarArtwork()
}
.navigationBarTitle(Text("Toolbar Name"), displayMode: .inline)
}
.frame(width: 320.0)
}
}
}
My result:
How can I change the state, while selecting different objects?
I need to do in a dynamic way (not hardcoded), so that when any object that is an image, it will display Image Toolbar, and so on.
I would do it like this :
Add a #State boolean variable for each state of your toolbar
#State private var textSelected = false
Add an .onTap event to each of the "active" views on your scene :
Text("Tap me!")
.tapAction {
self.textSelected = true
}
Make the appearance of you toolbar change based on the "state" variable(s)
if self.textSelected {
self.showTextToolbarContent()
}
Of course, showTextToolbarContent() is a local function returning a some View with the toolbar content tailored for text selection.
This is obviously only a bare-bone example. In your actual implementation you'll probably use an array of bools for the "selection" state and another state var for the view currently selected.