SwiftUI 不使用自动布局,如何为所有设备创建唯一大小的界面?

问题描述

我之前不使用 SwiftUI 并尝试创建媒体小部件,但我无法从另一个程序(YouTube 音乐媒体小部件)重新创建此小部件的副本,我在不同屏幕上的第四个单元格具有不同的边距,我不知道如何修复这个边距,因为 SwiftUI 没有自动布局。我在下面发布了我的小部件的代码,如果有人知道我做错了什么,请纠正我。

我的小部件和小部件的屏幕截图我想要的:

enter image description here

我的小部件代码

import WidgetKit
import SwiftUI
import Intents
import Foundation

struct Provider: IntentTimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {...}

    func getSnapshot(for configuration: ConfigurationIntent,in context: Context,completion: @escaping (SimpleEntry) -> ()) {...}

    func getTimeline(for configuration: ConfigurationIntent,completion: @escaping (Timeline<Entry>) -> ()) {...}
}

struct SimpleEntry: TimelineEntry {...}


struct WidgetTestEntryView : View {
    
    var entry: Provider.Entry
    
    var deeplinkURLFirst: URL {
        URL(string: "\(WIDGET_DEEP_LINK)0")!
    }
    
    let iconSize: CGFloat = 75.0
    var widgetLabel = "Favourites"
    let mainColor = Color(red: 0.218,green: 0.215,blue: 0.25)
    
    var body: some View {
        
        vstack(spacing: 0) {
            GeometryReader { geo in
                HStack(spacing: geo.size.width * 0.4){
                    Text(widgetLabel).foregroundColor(.white).font(.system(size: geo.size.width * 0.045,weight: .semibold,design: .default)).offset(y: 2)
                    Image("Label2").resizable().frame(width: geo.size.width * 0.15,height: 15,alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
                    
                }.frame(maxWidth: .infinity,maxHeight: geo.size.height * 0.7).background(Color.black).offset(y: -5)
            }
            
            GeometryReader { geo in
                HStack(spacing: geo.size.width * 0.1 / 7) {
    
                    Link(destination: deeplinkURLFirst) {
                        ZStack {
                            RoundedRectangle(cornerRadius: 10).foregroundColor(mainColor).frame(width: iconSize,height: iconSize)
                            
                            Image(base64String:"")?.resizable().frame(width: iconSize,height: iconSize)
                                .cornerRadius(10)
                                .background(mainColor).cornerRadius(10)
                        }.frame(width: iconSize,height: iconSize)
                    }
                    Link(destination: deeplinkURLFirst) {
                        ZStack {
                            RoundedRectangle(cornerRadius: 10).foregroundColor(mainColor).frame(width: iconSize,height: iconSize)
                            
                            Image(base64String: "")?.resizable().frame(width: iconSize,height: iconSize)
                    }

                    Link(destination: deeplinkURLFirst) {
                        ZStack {
                            RoundedRectangle(cornerRadius: 10).foregroundColor(mainColor).frame(width: iconSize,height: iconSize)
                    }
                    
                }.frame(width: .infinity,height: geo.size.height * 0.9,alignment: .leading).background(Color(red: 0.118,green: 0.118,blue: 0.15)).border(Color.white,width: 0).position(x: geo.size.width * 0.5,y: 20)
            }
            
            
            
        }.frame(maxWidth: .infinity,maxHeight: .infinity).background(Color(red: 0.118,blue: 0.15)).onAppear {
            print("all good")
            
        }
    }
}

@main
struct WidgetTest: Widget {
    let kind: String = WIDGET_PROJECT_NAME

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind,intent: ConfigurationIntent.self,provider: Provider()) { entry in
            WidgetTestEntryView(entry: entry)
        }
        .configurationdisplayName("Favourites")
        .description("Fast access to favoutires cagetory.")
        .supportedFamilies([.systemMedium])
    }
}

struct WidgetTest_Previews: PreviewProvider {
    static var previews: some View {
        WidgetTestEntryView(entry: SimpleEntry(date: Date(),configuration: ConfigurationIntent()))
            .previewContext(WidgetPreviewContext(family: .systemMedium))
    }
    
    
}


extension Image {
    init?(base64String: String) {
        guard let data = Data(base64Encoded: base64String,options: .ignoreUnkNownCharacters) else { return nil }
        guard let uiImage = UIImage(data: data) else { return nil }
        self = Image(uiImage: uiImage)
    }
}

解决方法

SwiftUI 为您自动布局,它与您应该一次支持所有屏幕的 UIKit 不同,这就是为什么间距会改变但您可以设置“规则”

//Prevents the repeating of code
struct ImageView: View {
    var deeplinkURLFirst: URL
    let mainColor: Color
    //Add another parameter for the image info I counldn't reprodice that
    var body: some View {
        Link(destination: deeplinkURLFirst) {
            ZStack {
                RoundedRectangle(cornerRadius: 10)
                    .foregroundColor(mainColor)
                    .overlay(
                    //Keep image within rectangle bounds
                    //The systemName stuff is just to replicate an actual image fill in with your image code
                    Image(systemName: "square")
                    .resizable())
            }
        }
        //Rule
        //Keep the images squares or you can set frame using your iconSize 
        //for all but the size of an iPhone 7 or SE is not the same 
        //as a ProMax it is best to set a ratio
        //If you fix the size padding will have to give way to adjust for larger/smaller screens.
        .aspectRatio(1,contentMode: .fit)

    }
}
struct WidgetTestEntryView : View {
    //var entry: Provider.Entry //I am just working with the View itself not a widget
    var deeplinkURLFirst: URL {
        URL(string: "\("WIDGET_DEEP_LINK")0")!
    }
    let iconSize: CGFloat = 75.0
    var widgetLabel = "Favourites"
    let mainColor = Color(red: 0.218,green: 0.215,blue: 0.25)
    let setSpacing: CGFloat = 4
    var body: some View {
        //Having multiple of GeometryReader just adds to the confusion look at the View as a whole vs pieces
        //Less is more with SwiftUI it is meant to support multiple screens
        //Set simple rules
        GeometryReader { geo in
        VStack(spacing: 0) {
                //Top portion
                HStack(spacing: geo.size.width * 0.4){
                    Text(widgetLabel).foregroundColor(.white).font(.system(size: geo.size.width * 0.045,weight: .semibold,design: .default))
                    //The systemName stuff is just to replicate an actual image fill in with your image code
                    Image(systemName: "square").resizable().foregroundColor(.white)
                        .frame(width: geo.size.width * 0.15,height: 15,alignment: .center)
                }
                //Using to many of these will end up causing conflicts
                //SwiftUI does a lot of the work for you
                .frame(maxWidth: .infinity,maxHeight: geo.size.height * (1/3))
                //Rule:
                //This will set the space between the boxes
                HStack(spacing: setSpacing)
                {
                    //Add another parameter for the image info I counldn't reproduce that without data
                    ImageView(deeplinkURLFirst: deeplinkURLFirst,mainColor: mainColor)
                    ImageView(deeplinkURLFirst: deeplinkURLFirst,mainColor: mainColor)
                }
                //Rule:
                //Keep the edge of the boxes from the edge of the screen/HStack
                //It is just a minimum so this will give if the space requries it to maintain ratio and spacing between boxes
                .padding(setSpacing)
                
                //This might need adjusting but the % of the top + the % of the bottom should == 1
                .frame(width: geo.size.width,height: geo.size.height * (2/3),alignment: .center)
                //This color needs to be adjusted to the right Color
                .background(Color(UIColor.darkGray))
            }
        }
        .background(Color(red: 0.118,green: 0.118,blue: 0.15))
        //Just to simualte widget size without crating a widget you shouldn't need it in your actual code
        .frame(maxWidth: 350,maxHeight: 150)
        .cornerRadius(20)
        .onAppear {
            print("all good")
            
        }
    }
}