Почему модификатор кадра по-прежнему влияет на другие представления при присоединении к нулевому изображению?

self.imagesViewModel.allImages[post.imageContentURL]?
    .resizable()
    .scaledToFill()
    .frame(width: 343, height: 171, alignment: .center)
    .clipShape(Rectangle())
    .shadow(radius: 1)

Предполагается, что этот код ничего не делает, когда возвращаемое значение изображения nil. Но по какой-то причине.frameмодификатор по-прежнему влияет на другие элементы внутри представления прокрутки. Это приводит к тому, что навигационные ссылки блокируются физически, но не визуально.

Я подтвердил, что когда .frameудаляется, все работает так, как я задумал. Но мне нужно установить ограничения на изображения, поэтому я не могу обойтись без чего-то подобного.

Я попытался разместить код внутри оператора if, чтобы проверить nilи отображать только при возвращении изображения, но это не сработало. Другие варианты этого кода также не работают.

if self.imagesViewModel.allImages[post.imageContentURL] != nil {
    self.imagesViewModel.allImages[post.imageContentURL]?
        .resizable()
        .scaledToFill()
        .frame(width: 343, height: 171)
        .clipShape(Rectangle())
        .shadow(radius: 1)
}

Я также пытался установить минимальные значения на ноль, но безуспешно.

Мне интересно, связано ли это с тем, что я использую Xcode 12 beta 5, или с использованием нового жизненного цикла приложения SwiftUI. Когда я запускаю код на своем iPhone (iOS 14), я получаю те же физические препятствия, но добавляю визуальный интервал между элементами scrollview.

Вот упрощенная версия моего кода, которая воспроизводит мою проблему:

import SwiftUI
import Combine

struct Post: Codable, Identifiable {
    var id: Int
    var text: String
    var imageContentURL: String?
}

class PostsViewModel: ObservableObject {
    @Published var posts: [Post] = []
}

class ImagesViewModel: ObservableObject {
    @Published var allImages: [String?: Image] = [:]
}

/// The View of the content displayed in Home
struct PostLayout: View {
    @EnvironmentObject var imagesViewModel: ImagesViewModel
    var post: Post
    
    init(post: Post) {
        self.post = post
    }
    
    var body: some View {
        VStack {
            // This is the navigation link that gets obstructed
            NavigationLink(destination: SomeOtherView()) {
                Image(systemName: "circle.fill")
                    .resizable()
                    .frame(width: 48, height: 48)
                    .clipShape(Circle())
            }
            
            Text(self.post.text)
            
            // MARK: This is the problem code, border added for clarity
            self.imagesViewModel.allImages[self.post.imageContentURL]?
                .resizable()
                .scaledToFill()
                .frame(width: 343, height: 171)
                .clipShape(Rectangle())
                .shadow(radius: 1)
                .border(Color.black, width: 1)
        }
        .border(Color.black, width: 1)
    }
}

/// View that is navigated to from Home
struct SomeOtherView: View {
    var body: some View {
        Text("SomeOtherView")
    }
}

/// Main displayed view
struct Home: View {
    @EnvironmentObject var postsViewModel: PostsViewModel
    @EnvironmentObject var imagesViewModel: ImagesViewModel

    var body: some View {
        NavigationView {
            ScrollView {
                ForEach(self.postsViewModel.posts) { post in
                    PostLayout(post: post)
                }
            }
            .onAppear() {
                // All displayed content created here
                var post1 = Post(id: 0, text: "Content")
                let post2 = Post(id: 1, text: "Content")
                var post3 = Post(id: 2, text: "Content")
                let post4 = Post(id: 3, text: "Content")
                let imageURL = "someImageURL"
                
                self.imagesViewModel.allImages[imageURL] = Image(systemName: "circle")
                
                // Only two posts are supposed to have an imageURL
                post1.imageContentURL = imageURL
                post3.imageContentURL = imageURL
                self.postsViewModel.posts.append(contentsOf: [post1, post2, post3, post4])
            }
            .navigationTitle("Home")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Home()
            .environmentObject(PostsViewModel())
            .environmentObject(ImagesViewModel())
    }
}

4 ответа

Решение

Оказывается, проблема не в nil или модификатор кадра.

Изображение фактически незримо выходило за границы .clipShape(Rectangle())и блокировал NavigationLink. Решение - использовать комбинацию.contentShape() а также .clipped.

self.imagesViewModel.allImages[self.post.imageContentURL]?
    .resizable()
    .scaledToFill()
    .frame(width: 343, height: 171)
    .contentShape(Rectangle())
    .clipped()
    .shadow(radius: 1)

Я обнаружил этот ответ из сообщения здесь: Обрезанное изображение вызывает TapAction за пределами кадра

Как вы уже заметили, .frameмодификатор не зависит от изображения. Он будет работать, даже если изображениеnil.

Что вы можете сделать, так это проверить, Imageможно загрузить. Вы можете сделать это, используяUIImage(named:) (который возвращает необязательный):

@ViewBuilder
var body: some View {
    if UIImage(named: "imageName") != nil {
        Image("imageName")
            .resizable()
            .scaledToFill()
            .frame(width: 343, height: 171, alignment: .center)
            ...
    }
}

или, альтернативно:

@ViewBuilder
var body: some View {
    if imagesViewModel.allImages[post.imageContentURL] != nil {
        imagesViewModel.allImages[post.imageContentURL]!
            .resizable()
            .scaledToFill()
            .frame(width: 343, height: 171, alignment: .center)
            ...
    }
}

РЕДАКТИРОВАТЬ

Нет необходимости в дополнительном String?в дикт. Просто замените его на:

@Published var allImages: [String: Image] = [:]

Затем используйте if оператор, чтобы проверить, не является ли изображение равным нулю (в Xcode 12 вы можете использовать if-let также):

if self.post.imageContentURL != nil {
    self.imagesViewModel.allImages[self.post.imageContentURL!]?
        .resizable()
        .scaledToFill()
        .frame(width: 343, height: 171)
        .clipShape(Rectangle())
        .shadow(radius: 1)
        .border(Color.black, width: 1)
}

Вы можете просто использовать if-let (Xcode 12+):

if let image = self.imagesViewModel.allImages[post.imageContentURL]? {
    image
    .resizable()
    .scaledToFill()
    .frame(width: 343, height: 171, alignment: .center)
    .clipShape(Rectangle())
    .shadow(radius: 1)
}

Скорее всего, на самом изображении есть проблемы, но представление типа SwiftUI Image существует. Image больше похоже UIImageView чем UIImage, хотя на самом деле бесполезно думать об этом как об одном из них. Вероятно, вы хотите:

    var body: some View {
        if imageView == nil {
            EmptyView()
        } else {
            imageView?
                .resizable()
                .scaledToFill()
                .frame(width: 343, height: 171, alignment: .center)
                .clipShape(Rectangle())
                .shadow(radius: 1)
                .border(Color.black)
        }
    }
Другие вопросы по тегам