在SwiftUI下对iPad进行适配

SwiftUI创建初衷之一便是可以高效、可靠的适配多个苹果的硬件平台。在健康笔记2.0开发初始,适配iPad便是我本次的设计目标之一。本文并非教程,只是我在进行本次开发中,对于适配iPad的一些教训和心得。

  • 我是谁
  • 躺着还是站着
  • 难以控制的NavigationView
  • Bug还是特别设计?
  • 布局优化

我是谁

app中的代码必须能高效、清晰的了解当前设备的状况,时刻搞清楚我是谁,我在哪,在干啥等等。因此在项目开始之初我便做了不少的准备并创建了一系列的代码。

比如,当前的运行设备:

enum Device {
    //MARK:当前设备类型 iphone iPad Mac
    enum Devicetype{
        case iphone,ipad,mac
    }
    
    static var deviceType:Devicetype{
        #if os(macOS)
        return .Mac
        #else
        if  UIDevice.current.userInterfaceIdiom == .pad {
            return .iPad
        }
        else {
            return .iPhone
        }
        #endif
 }

如果想要具体了解当前运行设备的型号,Github上有人提供了代码可以返回更精准的信息。

为了能够在View中方便的利用这些状态信息应对不同的情况,还需要继续做些准备。

extension View {
    @ViewBuilder func ifIs<T>(_ condition: Bool, transform: (Self) -> T) -> some View where T: View {
        if condition {
            transform(self)
        } else {
            self
        }
    }
    
    @ViewBuilder func ifElse<T:View,V:View>( _ condition:Bool,isTransform:(Self) -> T,elseTransform:(Self) -> V) -> some View {
        if condition {
            isTransform(self)
        } else {
            elseTransform(self)
        }
    }
}

这两段是我使用非常频繁的代码,在SwiftUI下,利用类似的代码可以非常容易的利用同一段代码应对各种不同的状况。

例如:

VStack{
     Text("hello world")
}
.ifIs(Deivce.deviceType == .iPhone){
  $0.frame(width:150)
}
.ifIs(Device.deviceType == .iPad){
  $0.frame(width:300)
}
.ifIs(Device.deviceType == .Mac){
  $0.frmae(minWidth:200,maxWidth:600)
}

只有解决了我是谁的问题,后面的工作才能更好的展开

躺着还是站着

因为健康笔记以列表被主要表现形式的app,最初所以我希望在iphone上始终保持Portrait,在ipad上保持Landscape的形式。不过最终还是决定让其在ipad上同时支持Portrait和Landscape。

ipadiPhone.png

为了更灵活的处理,我没有选择在info.plist中对其进行设定,而是通过在delegate中,针对不同的情况作出响应。

xcode.png

因为无需支持多窗口,所以关闭了multiple windows。另外需要激活Requires full screen才能让delegate作出响应

class AppDelegate:NSObject,UIApplicationDelegate{
  func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        return Device.deviceType == .iPad
            ? UIInterfaceOrientationMask.all
            : UIInterfaceOrientationMask.portrait
    }
}

在SwiftUI下如何设置Delegate请查看SwiftUI2.0 —— App、Scene及新的代码结构

如此便可以方便的控制自己想要的app呈现形态了。

难以控制的NavigationView

SwiftUI的NavigationView本身为了适配做了不少的工作,但效果并不好。

目前它支持两种style: StackNavigationView、DoubleColumnNavigationViewStyle,三种表现形式:单列、双列、以及三列(sidebar)。虽然看似覆盖了多数的应用,但由于没有提供更多的控制选项,因此用起来并不顺手。

比如,DoubleColumnNavigationViewStyle,在ipad上的竖屏和横屏时的呈现是不同的。左上角的隐藏按钮不可更改,不可取消。在包含sidebar的三列模式下,逻辑又有不同,不过按钮同样不提供任何替换、取消的能力。

NavigationLink只能在当前列中响应,另外并不提供控制列宽的能力。

如果想调整双列NavigationView的列宽,可以使用Introspect,参见介绍几个我在开发健康笔记2用到的Swift或SwiftUI第三方库

NavigationView{
  Text("hello")
}
.introspectNavigationController{ navigation in
    navigation.splitViewController?.maximumPrimaryColumnWidth = 360
    navigation.splitViewController?.preferredPrimaryColumnWidth = 500.0
}

为了能够让ipad在竖屏或横屏状态下都固定呈现双列的模式,并且左侧列不可折叠同时也不能出现我想要的折叠按钮,我使用了一个不得已的手段。伪造了一个双列显示的NavigationView。

针对不同的设备进入不同的rootView

struct HealthNotesApp:APP{
  var body: some Scene{
     rootView()
  }
  
  func rootView()-> some View{
        switch Device.deviceType {
        case .iPad:
            return AnyView(ContentView_iPad())
        default:
            return AnyView(ContentView_iPhone())
        }
    }
}

在ContentView_iPad中,使用类似代码伪造一个双列形式

HStack(spacing:0){
      ItemRootView(item: $item)
           .frame(width:height)
       Divider()
       ItemDataRootView()
            .navigationContent()
        }
.edgesIgnoringSafeArea(.all)

如此一来便拥有了上面图片中iPad的显示效果。状态基本上同DoubleColumnNavigationViewStyle的形式是完全一致的。分别都可以设置Toolbar,并且分割线也可以贯穿屏幕。

extension View{
    func navigationContent() -> some View{
        NavigationView{
            self
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

由于在Ipad下右侧列的视图同时被用在iphone下,在iPhone下它是由NavigationLink激活的,所以仍在NavigationView中,但在iPad下,需要明确的将在放置在NavigationView中。通过 .navigationContent,结合上面的isIf,便可以灵活的控制形态了。

另外需要针对iPhone和ipad的二级View激活进行分别处理,比如

if Device.deviceType  == .iPhone {
                    NavigationLink("", destination: ItemDataRootView(), isActive: $gotoDataList).frame(width:0,height:0)
            }

//在link的button中
Button("Item1"){
   store.item = item
   if Devie.deviceType == .iPhone {
       gotoDataList.toggle()
   }
}

//在ItemDataRootView中直接响应store.item即可

Bug还是特别设计?

某些SwiftUI的默认控件在iPad和iPhone下的运行效果和预期有较大差别,

比如ActionSheet:

当前AlertSheet在运行iOS14的ipad上的显示位置是几乎不可控的。箭头的位置,内容的显示,和预期都有巨大的差别。我不知道以后都会是这样还是目前的Bug。

个人不推荐当前在iPad上使用ActionSheet。最终只能在iPad下使用Alert替代了ActionSheet。如果一定要使用ActionSheet,popover或许是更好的选择。

ContextMenu目前在iPad上有响应上的问题,同样的指令在iPhone上没有问题,在iPad上会出现无法获取值的状况。同样不清楚是Bug还是其他原因。

比如

Text("click")
.contextMenu{
  Button("del"){
     delItem = item
     ShowActionSheet.toggle()
  }
}
.ActionSheet(isPresented:showActionSheet){
    delSheet(item:delItem)
}

这段代码在iphone上执行没有任何问题,不过在ipad上,delsheet很有可能会无法获取item。为了避免这个情况,目前只能做些特殊处理

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5){
                  showActionSheet.wrappedValue = true
}

类似上述的问题还有一些,只有当代码在ipad上跑起来多做测试才会发现其中的问题。

布局优化

由于健康笔记2.0在iPad上显示的左右两列,所以本来在iphone下运行没有问题View,在iPad下就会出现左右不对齐,不对称等问题。所以只要多调试,采用isIf多做处理,问题基本上都会比较容易获得解决。

仅举一例:

List{
   ...
}
.listStyle(InsetGroupedListStyle())

当它在iphone上作为独占屏幕的View时,显示很完美,但当它显示在IPad的右侧列时,Group上方的留空和左侧列的就不对齐,做点处理就ok了。

结尾

总之使用SwiftUI适配iPhone和iPad总体来说还是比较容易的。能否充分利用好各自设备的特点主要还是要在交互逻辑和UI设计上多下功夫,代码上的难度不大。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271