最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - Weird issue while showing an image in rounded rectangle - Stack Overflow

programmeradmin1浏览0评论

In my app, I need to show images in rounded rectangle shape on a horizontal scroll view and when I tap on the image, the image opens in full screen. I have multiple images, but for the same of keeping it simple, I have 2 images in my sample code below.

The second image (Image B) is very wide. To explain this question in an easy way, I have chosen first image (Image A) with 2 shades (yellow and red). If you tap on red color of Image A, the app behaves as if Image B was tapped and opens Image B instead of opening Image A. Tapping on yellow color of Image A, opens Image A correctly.

This is happening because Image B is wide and I am using image.resizeable().aspectRatio(**.fill**) to display images in a rounded rectangle shape. If I use image.resizeable().aspectRatio(**.fit**), then tap behavior works fine i.e. if red color of Image A is tapped, then app opens Image A itself, however with aspectRatio(.fit), the images don't get displayed as rounded rectangle.

Executable sample code:

import SwiftUI

struct Foo {
    var title: String
    var url: String
    var image: Image?
    
    init(title: String, url: String, image: Image? = nil) {
        self.title = title
        self.url = url
        self.image = image
    }
}

struct ContentViewA: View {
    @State private var data = [
        Foo(title: "Image A", url: ".jpg", image: nil),
        Foo(title: "Image B", url: ".jpg/800px-Sydney_Harbour_Bridge_night.jpg", image: nil)

        // Foo(title: "Image B", url: ".jpg", image: nil)
        /// There are more images in the array in real code.
    ]
    
    var body: some View {
        ZStack {
            Color.black.opacity(0.7).ignoresSafeArea()
            VStack {
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack(alignment: .center, spacing: 10) {
                        ForEach(Array(data.enumerated()), id: \.offset) { index, item in
                            if let urlObject = URL(string: item.url) {
                                AsyncImage(url: urlObject,
                                           scale: 1.0,
                                           transaction: Transaction(animation: .spring(response: 0.5, dampingFraction: 0.65, blendDuration: 0.025)),
                                           content: { renderPhoto(phase: $0, item: item, index: index) })
                            } else {
                                /// Note: Shows placeholder view
                                EmptyView()
                            }
                        }
                    }
                    .padding(.leading, 0)
                    .padding(.trailing, 16)
                    .frame(maxWidth: .infinity, minHeight: 65, maxHeight: 65, alignment: .topLeading)
                }
            }
            .padding([.top, .bottom], 150.0)
            .padding([.leading, .trailing], 50.0)
        }
    }
    
    @ViewBuilder
    private func renderPhoto(phase: AsyncImagePhase, item: Foo, index: Int) -> some View {
        switch phase {
            case .success(let image):
                thumbnailView(image: image, item: item, index: index)
            case .failure(let error):
                thumbnailView(item: item, index: index, isFailure: true)
            case .empty:
                thumbnailView(item: item, index: index, isFailure: true)
            @unknown default:
                EmptyView()
        }
    }
    
    private func thumbnailView(image: Image? = nil, item: Foo, index: Int, isFailure: Bool = false) -> some View {
        VStack {
            Rectangle()
            .foregroundColor(.clear)
            .frame(width: 72, height: 55)
            .background(
                VStack {
                    if let image = image {
                        image.resizable()
                            .aspectRatio(contentMode: .fill)
                            // .aspectRatio(contentMode: .fit) /// Setting aspect ratio to fit avoids the problem, but doesn't give rounded rectangle look.
                            .frame(width: 72, height: 55)
                            .disabled(false)
                            .clipped()
                    } else {
                        /// show error image
                        EmptyView()
                    }
                }
            )
            .cornerRadius(8)
            .padding([.top, .bottom], 10.0)
            .onTapGesture {
                print("%%%%% Tapped image title: \(item.title) and index is: \(index) %%%%%%")
            }
        }
    }
}

Sharing screenshots of the app using sample code:

Rounded rectangle images with aspectRatio(.fill), but tapping on red color of 1st image, opens 2nd image, because 2nd image is wide. This is the look of the images I want to have.

Images with aspectRatio(.fit), tapping on 1st image, opens 1st image and tapping on 2nd image, opens 2nd image. This is the tap behavior I want to have.

How can I have rounded rectangular looking images and also open correct image when tapped i.e. tapping anywhere on Image A should only open Image A?

In my app, I need to show images in rounded rectangle shape on a horizontal scroll view and when I tap on the image, the image opens in full screen. I have multiple images, but for the same of keeping it simple, I have 2 images in my sample code below.

The second image (Image B) is very wide. To explain this question in an easy way, I have chosen first image (Image A) with 2 shades (yellow and red). If you tap on red color of Image A, the app behaves as if Image B was tapped and opens Image B instead of opening Image A. Tapping on yellow color of Image A, opens Image A correctly.

This is happening because Image B is wide and I am using image.resizeable().aspectRatio(**.fill**) to display images in a rounded rectangle shape. If I use image.resizeable().aspectRatio(**.fit**), then tap behavior works fine i.e. if red color of Image A is tapped, then app opens Image A itself, however with aspectRatio(.fit), the images don't get displayed as rounded rectangle.

Executable sample code:

import SwiftUI

struct Foo {
    var title: String
    var url: String
    var image: Image?
    
    init(title: String, url: String, image: Image? = nil) {
        self.title = title
        self.url = url
        self.image = image
    }
}

struct ContentViewA: View {
    @State private var data = [
        Foo(title: "Image A", url: "https://www.shutterstock/image-illustration/two-shades-color-background-mix-260nw-2340299851.jpg", image: nil),
        Foo(title: "Image B", url: "https://upload.wikimedia./wikipedia/commons/thumb/e/ea/Sydney_Harbour_Bridge_night.jpg/800px-Sydney_Harbour_Bridge_night.jpg", image: nil)

        // Foo(title: "Image B", url: "https://www.shutterstock/image-photo/ultra-wide-photo-mountains-river-260nw-1755037052.jpg", image: nil)
        /// There are more images in the array in real code.
    ]
    
    var body: some View {
        ZStack {
            Color.black.opacity(0.7).ignoresSafeArea()
            VStack {
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack(alignment: .center, spacing: 10) {
                        ForEach(Array(data.enumerated()), id: \.offset) { index, item in
                            if let urlObject = URL(string: item.url) {
                                AsyncImage(url: urlObject,
                                           scale: 1.0,
                                           transaction: Transaction(animation: .spring(response: 0.5, dampingFraction: 0.65, blendDuration: 0.025)),
                                           content: { renderPhoto(phase: $0, item: item, index: index) })
                            } else {
                                /// Note: Shows placeholder view
                                EmptyView()
                            }
                        }
                    }
                    .padding(.leading, 0)
                    .padding(.trailing, 16)
                    .frame(maxWidth: .infinity, minHeight: 65, maxHeight: 65, alignment: .topLeading)
                }
            }
            .padding([.top, .bottom], 150.0)
            .padding([.leading, .trailing], 50.0)
        }
    }
    
    @ViewBuilder
    private func renderPhoto(phase: AsyncImagePhase, item: Foo, index: Int) -> some View {
        switch phase {
            case .success(let image):
                thumbnailView(image: image, item: item, index: index)
            case .failure(let error):
                thumbnailView(item: item, index: index, isFailure: true)
            case .empty:
                thumbnailView(item: item, index: index, isFailure: true)
            @unknown default:
                EmptyView()
        }
    }
    
    private func thumbnailView(image: Image? = nil, item: Foo, index: Int, isFailure: Bool = false) -> some View {
        VStack {
            Rectangle()
            .foregroundColor(.clear)
            .frame(width: 72, height: 55)
            .background(
                VStack {
                    if let image = image {
                        image.resizable()
                            .aspectRatio(contentMode: .fill)
                            // .aspectRatio(contentMode: .fit) /// Setting aspect ratio to fit avoids the problem, but doesn't give rounded rectangle look.
                            .frame(width: 72, height: 55)
                            .disabled(false)
                            .clipped()
                    } else {
                        /// show error image
                        EmptyView()
                    }
                }
            )
            .cornerRadius(8)
            .padding([.top, .bottom], 10.0)
            .onTapGesture {
                print("%%%%% Tapped image title: \(item.title) and index is: \(index) %%%%%%")
            }
        }
    }
}

Sharing screenshots of the app using sample code:

Rounded rectangle images with aspectRatio(.fill), but tapping on red color of 1st image, opens 2nd image, because 2nd image is wide. This is the look of the images I want to have.

Images with aspectRatio(.fit), tapping on 1st image, opens 1st image and tapping on 2nd image, opens 2nd image. This is the tap behavior I want to have.

How can I have rounded rectangular looking images and also open correct image when tapped i.e. tapping anywhere on Image A should only open Image A?

Share Improve this question edited Feb 1 at 7:57 Joakim Danielson 52.1k5 gold badges33 silver badges71 bronze badges asked Feb 1 at 4:38 tech_humantech_human 7,16617 gold badges75 silver badges134 bronze badges 1
  • More details: I removed ".aspectRatio(contentMode: .fill)" and ".aspectRatio(contentMode: .fit)" both and now only have image.resizable().frame(width: 72, height: 55).disabled(false).clipped(). This works fine and gives me rounded rectangle look and correct tapped behavior, but if Image B is more wide, then it looks like a squished image. So, still looking for a better solution. I updated URL for image B in my above question to display a more wider image. – tech_human Commented Feb 1 at 4:59
Add a comment  | 

1 Answer 1

Reset to default 1

I've run into this behavior before. clipped() may visually clip the view, but it still accepts clicks/taps.

To solve this, you can add .allowsHitTesting(false) to the Image.

Then, you'll need to add .contentShape(.rect) (or a rounded rect if you want) to your Rectangle, since otherwise, the clear color it has means that it won't accept hits.

Here's the modified thumbnail view:

private func thumbnailView(image: Image? = nil, item: Foo, index: Int, isFailure: Bool = false) -> some View {
    VStack {
        Rectangle()
            .foregroundColor(.clear)
        .frame(width: 72, height: 55)
        .background(
            VStack {
                if let image = image {
                    image.resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(width: 72, height: 55)
                        .clipped()
                        .allowsHitTesting(false) // <-- Here
                } else {
                    /// show error image
                    EmptyView()
                }
            }
        )
        .cornerRadius(8)
        .padding([.top, .bottom], 10.0)
        .contentShape(.rect) // <-- Here
        .onTapGesture {
            print("%%%%% Tapped image title: \(item.title) and index is: \(index) %%%%%%")
        }
    }
}
发布评论

评论列表(0)

  1. 暂无评论