SwiftUI 不使用自动布局,如何为所有设备创建唯一尺寸的界面?
SwiftUI doesn't use auto layout, how to create a unique size interface for all devices?
我以前不使用 SwiftUI 并尝试创建中型小部件,但我无法从另一个程序(YouTube 音乐中型小部件)重新创建此小部件的副本,我在不同屏幕上的第四个单元格具有不同的边缘边距,我不知道如何解决这个边距,因为 SwiftUI 没有自动布局。我把我的小部件代码贴在下面,如果有人知道我做错了什么,请指正。
我的Widget截图和我想要的widget:
我的插件代码:
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, in context: Context, 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)
.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)
.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)
.cornerRadius(10)
.background(mainColor).cornerRadius(10)
}.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, green: 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)
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")
}
}
}
我以前不使用 SwiftUI 并尝试创建中型小部件,但我无法从另一个程序(YouTube 音乐中型小部件)重新创建此小部件的副本,我在不同屏幕上的第四个单元格具有不同的边缘边距,我不知道如何解决这个边距,因为 SwiftUI 没有自动布局。我把我的小部件代码贴在下面,如果有人知道我做错了什么,请指正。
我的Widget截图和我想要的widget:
我的插件代码:
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, in context: Context, 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)
.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)
.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)
.cornerRadius(10)
.background(mainColor).cornerRadius(10)
}.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, green: 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)
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")
}
}
}