I am a beginner to Swift, SwiftUI, and using Xcode. I have been doing a couple of online tutorials and came across a problem when doing this bonus challenge.
import SwiftUI
struct ContentView: View {
#State var userCredits = 1000
#State var slot1 = 1
#State var slot2 = 1
#State var slot3 = 1
var body: some View {
VStack {
Text("SwiftUI Slot!")
.font(.system(size: 38))
.fontWeight(.medium)
Spacer()
Text("Credits: \(userCredits)")
Spacer()
HStack{
Image("apple\(slot1)")
.resizable()
.aspectRatio(contentMode: .fit)
Image("cherry\(slot2)")
.resizable()
.aspectRatio(contentMode: .fit)
Image("star\(slot3)")
.resizable()
.aspectRatio(contentMode: .fit)
}
Spacer()
Button("Spin") {
slot1 = Int.random(in: 1...3)
slot2 = Int.random(in: 1...3)
slot3 = Int.random(in: 1...3)
if slot1 == slot2 && slot2 == slot3{
userCredits += 15
}
else {
userCredits -= 5
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The images of the apple, cherry, and star disappear when I put the (slot#) after it
I tried taking out the (slot#) but that will simply display the picture of the images and not change them nor make the game work as intended
You could do this by declaring an array of image names:
let imageNames = ["apple", "cherry", "star"]
then use something like:
HStack{
Image(imageNames[slot1])
.resizable()
.aspectRatio(contentMode: .fit)
Image(imageNames[slot2])
.resizable()
.aspectRatio(contentMode: .fit)
Image(imageNames[slot3])
.resizable()
.aspectRatio(contentMode: .fit)
}
Also, don't forget that Swift arrays are zero based, so your spin function should be:
slot1 = Int.random(in: 0..<3)
slot2 = Int.random(in: 0..<3)
slot3 = Int.random(in: 0..<3)
What you need is to add your images into an array and then use the values for your slot# properties as indices for getting an image from the array
So add an array property first to your view
let images = ["apple", "cherry", "star"]
Then change the call to Image(...) to use this array
Image(images[slot1 - 1])
and so on for the other two
Related
I am trying to develop a star rating view using SwiftUI. I have a maximum of 5 stars, and the tricky part is that I need to fill 4.7 stars. So the last star should only fill up 70% of its foreground color.
Below is the code that I have done so far
import SwiftUI
struct AverageStarsView: View {
#Binding var rating: Double
var body: some View {
Group {
StarView()
}
.frame(maxHeight: 16)
}
}
struct AverageStarsView_Previews: PreviewProvider {
static var previews: some View {
AverageStarsView(rating: .constant(4.7))
}
}
struct StarView: View {
private var fillColor = .yellow
private var emptyColor = .grey
var body: some View {
ZStack {
if let starImage = UIImage(named: "icon-star", in: .sharedResources, compatibleWith: nil) {
Image(uiImage: starImage)
.resizable()
.renderingMode(.template)
.foregroundColor(emptyColor)
.aspectRatio(contentMode: .fit)
}
}
}
}
struct StarView_Previews: PreviewProvider {
static var previews: some View {
StarView()
}
}
How can fill only 70% of the star view.
I can't use .overlay because I need to support iOS 14.
Sample view
You can use the .mask() modifier that will create a mask of your view with a given view. Here is what you can achieve for a single star:
struct StarView: View {
var fillValue: Double
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
Rectangle()
.fill(Color.orange)
.frame(width: geometry.size.width * fillValue)
}
}
.mask(
Image(systemName: "star.fill")
.resizable()
)
.aspectRatio(1, contentMode: .fit)
}
}
Here is the result with the fillValue set to 0.7:
You can then stack them into an HStack and make a quick calculation to know the fill value of the last star to make your five stars rating component.
Make sure to use .mask() and not .mask { } as the last one require iOS 15.0 to be used.
Before I select an image from the gallery, I want to have an initial image from SF-Symbole ("photo.fill). But unfortunately it shows me an error.
Failed to produce diagnostic for expression; please submit a bug
report (https://swift.org/contributing/#reporting-bugs) and include
the project
The code:
import SwiftUI
struct ContentView: View {
#State private var showImagePicker : Bool = false
#State private var image : Image? = nil
var body: some View {
NavigationView{
VStack {
Spacer()
if image? {
Image(uiImage: image?)
.resizable()
.scaledToFit()
.frame(minWidth: 0, maxWidth: .infinity)
} else {
Image(systemName: "photo.fill")
.resizable()
.scaledToFit()
.opacity(0.6)
.frame(minWidth: 0, maxWidth: .infinity)
.padding(.horizontal)
}
Spacer()
Button("Öffne Galerie"){
self.showImagePicker = true
}.padding()
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(10)
}
.sheet(isPresented: self.$showImagePicker) {
PhotoCaptureView(showImagePicker: self.$showImagePicker, image: self.$image)
}
.navigationBarTitle(Text("Foto bearbeiten"))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
As I mentioned in my comment you are unwrapping the optional image incorrectly, you need to use an if-let so that you have a non-nil value for your image.
You are also passing an Image where you actually require a UIImage. This is not something that you can do. You need to make sure that you understand that types that you using and what they represent.
Unfortunately you did not include the code for your PhotoCaptureView so it is not possible to see what you actually require. This is why you are asked to provide a minimum reproducible example when you post a question on SO.
However, here is how you can handle it if it requires an Image or a UIImage, there are similarities to both. Look at the comments in the code for the changes.
PhotoCaptureView uses Image
If you are creating an Image in the PhotoCaptureView then there are two changes that you need to make.
You need to unwrap your Image before you use it
As you have a SwiftUI Image you can just use it directly.
struct ContentView: View {
#State private var showImagePicker : Bool = false
#State private var image : Image? = nil
var body: some View {
NavigationView{
VStack {
Spacer()
if let image = image { // here we unwrap the Image
image // as we have a SwiftUI Image we can use it directly.
.resizable()
.scaledToFit()
.frame(minWidth: 0, maxWidth: .infinity)
} else {
Image(systemName: "photo.fill")
.resizable()
.scaledToFit()
.opacity(0.6)
.frame(minWidth: 0, maxWidth: .infinity)
.padding(.horizontal)
}
Spacer()
Button("Öffne Galerie"){
self.showImagePicker = true
}.padding()
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(10)
}
.sheet(isPresented: self.$showImagePicker) {
PhotoCaptureView(showImagePicker: self.$showImagePicker, image: self.$image)
}
.navigationBarTitle(Text("Foto bearbeiten"))
}
}
}
PhotoCaptureView uses UIImage
However, if your PhotoCaptureView requires a UIImage then you need to make three changes to your code.
Firstly we would need to change your #State variable from being Image into a UIImage.
We can then unwrap the image the same way as above
We then pass the unwrapped UIImage into the initializer for Image(uiImage:)
struct ContentView: View {
#State private var showImagePicker : Bool = false
#State private var image : UIImage? = nil // Here we use UIImage
var body: some View {
NavigationView{
VStack {
Spacer()
if let image = image { // We unwrap the UIImage so that we can use it
Image(uiImage: image) // Here we convert the UIImage into a SwiftUI Image
.resizable()
.scaledToFit()
.frame(minWidth: 0, maxWidth: .infinity)
} else {
Image(systemName: "photo.fill")
.resizable()
.scaledToFit()
.opacity(0.6)
.frame(minWidth: 0, maxWidth: .infinity)
.padding(.horizontal)
}
Spacer()
Button("Öffne Galerie"){
self.showImagePicker = true
}.padding()
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(10)
}
.sheet(isPresented: self.$showImagePicker) {
PhotoCaptureView(showImagePicker: self.$showImagePicker, image: self.$image)
}
.navigationBarTitle(Text("Foto bearbeiten"))
}
}
}
I would suggest that you read up on Optionals in the Swift documentation, as you seem to have some misconceptions about them.
I have a profile picture that I am attempting to show a default image until a user selects a picture from a picker (say that three times fast). In WebImage, I point to a view Model, which connects to my database to grab the user image. If no image exists I currently just have it on an empty string.
For reference, this is the line that serves up the image
WebImage(url: URL(string: vm.userModel?.profilePictureURL ?? "")
I tried adding a bool that updated on the onChange function to true and tried wrapping the webImage in an IF statement, but the view didn't update correctly.
import SwiftUI
import Firebase
import FirebaseFirestore
import SDWebImageSwiftUI
struct ProfilePicture: View {
#ObservedObject var vm = DashboardLogic()
#State private var showingImagePicker = false
#State private var inputImage: UIImage?
var body: some View {
ZStack (alignment: .topTrailing){
VStack{
if let inputImage = inputImage {
Image(uiImage: inputImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width:150, height: 150)
.clipShape(Circle())
} else{
WebImage(url: URL(string: vm.userModel?.profilePictureURL ?? ""))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width:150, height: 150)
.clipShape(Circle())
}
Button(action: {
showingImagePicker = true
}){
Image(systemName:("plus.circle.fill"))
.resizable()
.aspectRatio(contentMode: .fill)
.foregroundColor(Color("ButtonTwo"))
.frame(width:30, height:25)
.contentShape(Rectangle())
}
//PRESENT PICKER
}
.sheet(isPresented: $showingImagePicker){
EditorImagePicker(image: $inputImage)
}
}
//SAVE IMAGE TO DATABASE (FIREBASE)
.onChange(of: inputImage, perform: { _ in
persistImageToStorage() //call to save function
})
}
}
}
My SwiftUI app displays images from an external url properly using
LazyVGrid(columns: columns, alignment: .center, spacing: 10) {
ForEach(0..<14) { i in
AsyncImage(url: url) { image in
VStack {
image
.resizable()
.scaledToFill()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity)
.cornerRadius(10)
.onTapGesture {
selectedItem = ImageSelection(name: url!.path)
print(selectedItem?.name as Any)
}
}
}
.sheet(item: $selectedItem) { item in
Image(item.name)
}
But the sheet that comes up from the .onTapGesture is blank. How can I properly get the url path so the image displays on the new sheet? Thanks!
EDIT
Ultimately this view is displaying images from https://picsum.photos. I'm trying to determine the actual URL of the displayed images.
as #Asperi mentioned, you could use another AsyncImage to again download the image. Try the following code, which
fixes some of the inconsistencies in your code and also loops over
the url id (as per your latest thought) to download each different ones:
struct ImageSelection: Identifiable {
let id = UUID()
var url: URL? // <-- note
}
struct ContentView: View {
let columns:[GridItem] = Array(repeating: .init(.flexible(), spacing: 5), count: 3)
#State var selectedItem: ImageSelection?
var body: some View {
ScrollView {
LazyVGrid(columns: columns, alignment: .center, spacing: 10) {
ForEach(0..<14) { i in
let url = URL(string: "https://picsum.photos/id/\(i)/200")
AsyncImage(url: url) { phase in
if let image = phase.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity)
.cornerRadius(10)
.onTapGesture {
selectedItem = ImageSelection(url: url)
}
}
else {
Image(systemName: "smiley")
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 50))
}
}
}
}
.sheet(item: $selectedItem) { item in
AsyncImage(url: item.url)
}
}
}
}
So I have an array of flowers.
var flowers = ["Waterlily", "Sunflower", "Rose", "Magnolia", "Lily", "Jonquil", "Echinacea"]
I am trying to convert these strings in the array to lowercase, and append .jpg at the end of them so I can display the pictures in my resources folder.
Here is my code for that:
Image(UIImage(named: flowers[index].lowercased() + ".jpg"))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 60)
My error is that I cannot convert value of type 'UIImage?' to expected argument type 'String'.
Here's my full code:
import PlaygroundSupport
struct ContentView: View
{
var flower: String = ""
var flowers = ["Waterlily", "Sunflower", "Rose", "Magnolia", "Lily", "Jonquil", "Echinacea"]
var body: some View
{
List(0..<flowers.count)
{
index in
HStack
{
Image(UIImage(named: flowers[index].lowercased() + ".jpg"))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 60)
Text(self.flowers[index])
}
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
You are using the wrong initialiser for the Image View.
use Image(uiImage: UIImage) instead in that case. (Note that the parameter name / argument label is required.)
https://developer.apple.com/documentation/swiftui/image/init(uiimage:)
(changed your code to work)
struct ContentView: View
{
var flower: String = ""
var flowers = ["Waterlily", "Sunflower", "Rose", "Magnolia", "Lily", "Jonquil", "Echinacea"]
var body: some View
{
List(0..<flowers.count) { index in
HStack {
// uiImage argument label is required
Image(uiImage: UIImage(named: flowers[index].lowercased() + ".jpg"))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 60)
Text(self.flowers[index])
}
}
}
}
Here is a list of all initialisers for the Image View
https://developer.apple.com/documentation/swiftui/image
Swift documentation for function parameters and argument labels
https://docs.swift.org/swift-book/LanguageGuide/Functions.html#ID166