I'm new to the development of Swift. I've done several courses but I have some questions that I can't find.
I want to design an application that has 50 or more images, with a previous and next button to go through them in a matrix.
I've looked at random snippets of sample code, but I'm having a hard time figuring out how to do it. What's the easiest and most efficient method to go back and forth between these images in a view? The other problem I have is that I want to be able to save the image when it is selected.
Surely the answer to this is easy for you, but I'm a beginner, and I can't find a solution, after two days of searching and trying.
Thanks in advance!
Finotoni
There is some steps you need to do:
Get/ define your images
Have methods (or another way) of getting the previous and next image
Displaying the current image
Here is an really easy example that should help you understand:
struct ContentView: View {
// Your images
#State var images = ["image1", "image2", "image3"]
// Some indicator on what image is the current one
#State var imageIndicator: Int = 0
var body: some View {
HStack {
// Prev one
Button(action: {
// Call a method that gets you the previous image
if self.imageIndicator == 0 {
self.imageIndicator = self.images.count - 1
} else {
self.imageIndicator = self.imageIndicator - 1
}
}) {
Image(systemName: "arrow.left")
}
// Image with indicator to what image to display
Image(self.images[self.imageIndicator]).resizable().frame(width: 100, height: 100)
// Next one
Button(action: {
// Call a method that gets you the next image
if self.imageIndicator == self.images.count - 1 {
self.imageIndicator = 0
} else {
self.imageIndicator = self.imageIndicator + 1
}
}) {
Image(systemName: "arrow.right")
}
}
}
}
Produces this:
Related
Is it possible to detect the cell (or array position) of the List currently displayed on the View screen from among the 100 Items as shown below?
Also, at that time, I would like to get the position of the cell in detail, such as some cells that are half hidden from the screen at the top and bottom.
struct DemoList: View {
// 1.
#State private var items: [Item] = (0..<100).map { Item(title: "Item #\($0)") }
// 2.
var body: some View {
List {
ForEach(items) { item in
Text(item.title)
}
}
}
}
This might be a starting point. But beware that List always keeps one "buffer" cell before .onAppear/.onDisappear kick in.
List {
ForEach(items) { item in
GeometryReader { geo in
Text(item.title)
.onAppear {
print(item.title, geo.frame(in: .global))
}
.onDisappear {
print(item.title, "---")
}
}
}
}
A more sophisticated approach would use PreferenceKey. I recommend this (look for "GridInfoPreference"): https://swiftui-lab.com/impossible-grids/
Try using ‘onAppear’ modifier as Text(text).onAppear {}
Since iOS 15 there have been improvements around that modifier
What I'm trying to accomplish
A vertical list of TextField views that user enters values in one at a time. After entering a value in a text field and pressing keyboard submit button the focus moves to the next text field.
The problem I'm facing
The keyboard obstructs the active text field. I was hoping that if I used ScrollView it would automagically scroll to the next focused text field, but that's not the case.
Then I decided to try using ScrollViewReader's ScrollViewProxy object to scroll to the focused text field, which works! But not quite. It scrolls to the selected text field, but not enough to go over the keyboard. It seems to always be just a little bit underneath the top of the keyboard (regardless of whether there's keyboard toolbar or autocorrection bar or nothing).
Here's a screenshot of roughly where the text field ends up after I call proxy.scrollTo(focusedInput):
Here's where I would want it to end up:
Minimal code sample
struct ContentView: View {
#State var inputsValues: [String]
#FocusState var focusedInput: Int?
init() {
inputsValues = (0..<30).map { _ in "" }
}
var body: some View {
ScrollViewReader { proxy in
ScrollView {
VStack {
ForEach((0..<inputsValues.count), id: \.self) { i in
TextField("Value", text: $inputsValues[i])
.focused($focusedInput, equals: i)
.submitLabel(.next)
.id(i)
.onSubmit {
if (i + 1) < inputsValues.count {
focusedInput = i + 1
proxy.scrollTo(focusedInput)
} else {
focusedInput = nil
}
}
}
}
}
}.toolbar {
ToolbarItem(placement: .keyboard) {
Text("This is toolbar")
}
}
}
}
Chaning two+ states in one closure (when they are related somehow or affects one area) are handled not very good (or not reliable). The fix is to separate them in different handlers.
Tested with Xcode 13.3 / iOS 15.4
Here is main part:
VStack {
// ... other code
.onSubmit {
// update state here !!
if (i + 1) < inputsValues.count {
focusedInput = i + 1
} else {
focusedInput = nil
}
}
}
}
.onChange(of: focusedInput) {
// react on state change here !!
proxy.scrollTo($0)
}
Complete test module is here
I have a tabBar with 5 options, two of the options must be shown as a side menus(with clear background color), which means they have a previously selected view being seen as a background.
I have the only idea of making it with a ZStack which consists of three views and changing the opacity of the last two.
VStack {
if selectedTab == .firstOpaque {
ZStack {
firstOpaqueView()
firstClearView().opacity(firstViewOpacity)
secondClearView().opacity(secondViewOpacity)
}
} else if selectedTab == .secondOpaque {
ZStack {
secondOpaqueView()
firstClearView().opacity(firstViewOpacity)
secondClearView().opacity(secondViewOpacity)
}
} else if selectedTab == .thirdOpaque {
ZStack {
thirdOpaqueView()
firstClearView().opacity(firstViewOpacity)
secondClearView().opacity(secondViewOpacity)
}
}
CustomTabBarView()
}
But this seems inefficient. Is there any other way or this one is the only?
I have a game view that has a board and I have several parts in a VStack and the final 2 HStack are some 'buttons' and then a row of tiles for the game.
VStack(spacing: 5) {
HStack {}
HStack {}
......
......
HStack {
ResetButton {
self.returnLetters()
}
NewLine {
self.newLine()
}
CalcButton {
self.calcButton()
}
StartButton {
self.startButton()
}
}
HStack {
ForEach(0..<7) { number in
Letter(text: self.tray[number], index: number, onChanged: self.letterMoved, onEnded: self.letterDropped)
}
}
This sets the screen all very well However I would ideally not want to show the Start and Calc buttons until later in the game, or indeed replace the Reset and NewLine buttons with Start and Calc.
As it looks
Ideally What I would have until later in game
Changing to this on the last go
Is there a way to not show items in the Stack or add items to the stack later please?
thanks
Use #State boolean variable that define if the buttons should be shown or not, when you change their value the view will be refreshed.
#State var isResetVisible = true
...
if isResetVisible {
ResetButton {
self.returnLetters()
}
}
then you set it to false somewhere and it will hide
Here is a pattern, introduce corresponding #State variable with desired initial state value. The state can be changed later at any time somewhere in view and view will be updated automatically showing conditionally dependent button:
#State private var newLineVisible = false // initially invisible
...
var body: some View {
...
HStack {
ResetButton {
self.returnLetters()
}
if self.newLineVisible { // << include conditionally
NewLine {
self.newLine()
}
}
CalcButton {
self.calcButton()
}
I'm trying to make a dynamic view to answer some questions. Those questions are created from my back office, ant the next question depends on the current user answer. So I have to build my view as things progress.
Here is a example :
First Question :
Are you alive ? : | Are you sure ?
|
YES/NO (no chosen) | YES/NO
I made a subview with the different type of question, and a container view to embed the path like that :
Here is the code of my container View, the white block is embed in a Scrollview
#ObservedObject var VM:SurveyBrowserViewModel
#State private var offsetX:CGFloat = 0
#State private var opacity:Double = 1
#State var myView:AnyView = AnyView(EmptyView())
var body: some View {
ZStack{
Image("background_survey_canyon")
.resizable()
backgroundTriangle
.opacity(0.65)
ScrollView(showsIndicators:false){
myView
.padding(.top, 55 + safeAreaTop)
.padding(.bottom, 60 + safeAreaBottom)
.offset(x : offsetX , y: 0)
.opacity(opacity)
.onReceive(VM.nextQuestionPassthrough) { (v) in
self.myView = v
}
.onReceive(VM.nextQuestionValuePassthrough) { (v) in
self.offsetX = v
self.opacity = 0
withAnimation(.spring()) {
self.offsetX = 0
self.opacity = 1
}
}
}
.frame(maxWidth: .infinity,maxHeight: .infinity)
}
}
and here is the viewModel section which updates the View state, current question is the question which needs to be displayed according the good type.
var nextQuestionPassthrough = PassthroughSubject<AnyView,Never>()
/// Get The good view to match the correct question tyoe
/// - Parameter answerSelected: Binding bool to hide/show next button visibility
private func getGoodView(){
guard (currentQuestion != nil) else { nextQuestionPassthrough.send(AnyView(EmptyView())); return }
switch QuestionType.intToQuestionType(value: currentQuestion!.type!){
case .YesOrNo :
if currentQuestionViewModel == nil{
currentQuestionViewModel = YesNoQuestionViewModel(question: currentQuestion!,temporaryId: temporaryId)
}
nextQuestionPassthrough.send(AnyView(YesNoQuestionView(VM: currentQuestionViewModel as! YesNoQuestionViewModel,answerSelected: nextViewState! )))
}
}
I've tried different way to achieve what I'm expecting :
I want a animation like a auto scroll coming form right to left, but it's cosmetic and it's not my main problem.
My problem is that when i send view from the nextQuestionPassthrough the init section from my new view is called but not the appear one. So all state are saved as we can see on the picture ( yes and no answer are two #State set to false at initialization but stay selected when I send the new view. I tried several ideas, like a published view instead of a #State but the behavior is the same.
I'm thinking about a new approch, with my container embed two views to make the good animation and the good life cycle ( init then appear )
Has anyone ever studied this problem or done something similar?