· 20 days ago
如何让你的 Hotwire Native 应用在 iOS 和 Android 上完美运行,无需大量 Swift 或 Kotlin 代码。
多年来,我发现 Hotwire Native 应用中最大的用户体验提升往往来自最细微的改动。并非完全原生化的界面,甚至也不是桥接组件。而是一些细微而精心设计的决策,让你的 Rails 视图在 iOS 和 Android 上都能完美呈现。
在这篇深度解析中,我将分享我几乎在所有开发应用中都会应用的六项改进 。这些改进快速、简单,却能显著提升用户体验,使其更接近原生应用:
<h1> 标签升级为原生标题(不破坏缓存)Hotwire 原生应用相比移动网页的一大优势在于能够集成系统数据或 API。幸运的是,从设备中获取照片就像编写 HTML 代码一样简单。
在表单中添加一个可以接受图片的 <input> 标签:
<%= form.file_field :image, accept: "image/jpg,image/jpeg,image/png" %>
就这么简单!这个功能支持 iOS 和 Android 系统。用户可以从相册中选择照片,拍摄新照片,甚至可以从设备中选择文件。
文件被选中后,它会填充 <input> 字段并随表单一起提交,就像在网页上一样。Active Storage 直接上传也同样适用。
请注意,您还需要更新 iOS 设备上的 Info.plist 文件。添加 NSCameraUsageDescription 和 NSMicrophoneUsageDescription 以允许访问照片图库并启用麦克风/视频录制。 文末链接提供了完整的源代码,其中包含您所需的一切。
如果你每次启动应用都需要登录,那么这篇文章正适合你。关键在于设置一个有效期较长的 cookie ,使其在多次启动后仍然有效。
想知道如何在 Hotwire Native 应用中处理身份验证吗? 以下是我使用基于 cookie 的身份验证的具体方法:
如果您直接管理 cookie,则可以使用 cookies.permanent 设置一个“永久” cookie ,有效期为两年。
cookies.permanent.encrypted[:user_id] = user.id
在底层,Hotwire Native 会在每次页面成功加载后自动将所有 cookie 复制到磁盘。但它会忽略会话 cookie。然而,桌面浏览器有时会保持这些 cookie 的有效期,这就是为什么您在网页上保持登录状态,但在应用程序中却无法登录的原因。
在 Devise 中,您可以通过确保 Hotwire Native 应用的登录表单中始终选中“记住我”来执行类似的操作:
<%= form_with url: session_path do |form| %>
<% if hotwire_native_app? %>
<%= form.hidden_field :remember_me, value: true %>
<% else %>
<%= form.check_box :remember_me %>
<% end %>
<% end %>
在原生 iOS 和 Android 应用中,我们可以将屏幕以模态框的形式呈现 ,使其从底部向上滑动,而不是从侧边滑入。模态框提供专注的、中断式的体验,非常适合简短、独立的任务。
我们可以在 Hotwire Native 应用中利用这种 UX 模式,将表单以模态框的形式呈现 ,从而改进:
iOS 上的默认屏幕呈现方式与模态呈现方式。
要将表单以模态框形式呈现,我们需要设置路径配置规则。路径配置用于远程更改应用程序的行为。在这里,我们将使用它来指定哪些 URL 应该以模态框形式呈现。
首先,告诉应用在哪里可以找到配置信息。在 iOS 系统中, 需要在应用启动后立即在 AppDelegate 中进行配置:
Hotwire.loadPathConfiguration(from: [
.server(rootURL.appending(path: "configuration/ios.json"))
])
在 Android 系统中,在 Application 子类中:
Hotwire.loadPathConfiguration(
context = this,
location = PathConfiguration.Location(
remoteFileUrl = "$rootUrl/configuration/android.json"
)
)
回到服务器端,向 config/routes.rb 中添加一条新路由 :
Rails.application.routes.draw do
resource :configuration, only: :show
end
最后,在 ConfigurationsController 中应用实际配置 :
class ConfigurationsController < ApplicationController
def show
render json: {
settings: {},
rules: [
{
patterns: [
"/new$",
"/edit$"
],
properties: {
context: "modal"
}
}
]
}
end
end
现在,任何以 /new 或 /edit 结尾的访问 URL (通常的表单式端点)都将在应用程序中以模态框的形式显示。
请查看文末提供的源代码,以获得更强大的解决方案,包括版本控制和针对 Android 与 iOS 的特定配置。
<h1> 标签升级为原生标题您的 Web 应用很可能在每个屏幕上都渲染一个 <h1> 标签。但原生应用已经有内置的标题显示方式:在导航栏中。
iOS 和 Android 设置屏幕上的原生屏幕标题示例。
我们将利用原生标题,通过动态渲染和一个仅在 Hotwire Native 中渲染的条件 CSS 文件来实现。最后,我们会提供一种动态注入 data-* 属性的方法,使其不会影响缓存。
首先,在布局文件中,确保 HTML 的 <title> 标签使用 content_for 属性动态设置 。Hotwire Native 应用默认会将 <title> 渲染到原生导航栏。 然后,有条件地链接一个自定义样式表:
<html>
<head>
<title><%= content_for(:title) %></title>
<%= stylesheet_link_tag :application %>
<% if hotwire_native_app? %>
<%= stylesheet_link_tag :native %>
<% end %>
</head>
<body>
<!-- ... -->
</body>
</html>
在渲染 Hotwire Native 内容时, native.css 中的所有内容都将优先于 application.css 。创建一个新的 CSS 类来隐藏该元素:
.d-hotwire-native-none {
display: none;
}然后装饰你的 <h1> 元素,并通过 content_for 设置 <title> :
<% content_for :title, title %>
<h1 class="d-hotwire-native-none"><%= title %></h1>
以下是修改前后的对比图。左侧是通用的原生标题,其中 <h1> 的位置很别扭 。右侧则充分利用了导航栏中的原生标题。效果好多了!
搬家前后<h1>给原生标题添加标签。
如果您使用的是 Tailwind CSS,我们可以利用自定义变体来实现同样的效果。请使用新的 @custom-variant 定义更新 Tailwind 配置 :
@import "tailwindcss";
@custom-variant hotwire-native {
body[data-hotwire-native] & {
@slot;
}
}
为了实现这一点,我们需要在每个页面的 <body> 元素中添加 data-hotwire-native 。我们不使用 ERB 在服务器端执行此操作(这可能会破坏缓存),而是通过 JavaScript 中的 Stimulus 控制器添加它:
// app/javascript/controllers/hotwire_native_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
if (navigator.userAgent.includes("Hotwire Native")) {
document.body.setAttribute("data-hotwire-native", "")
}
}
}
然后将其连接到我们的应用程序布局中:
<html>
<head>
<!-- ... -->
</head>
<body data-controller="hotwire-native">
<!-- .. -->
</body>
</html>
现在我们可以像使用 Tailwind CSS 中的其他变体一样使用 hotwire-native: ,使其仅应用于应用程序:
<div class="hidden hotwire-native:block"></div>
<div class=”bg-orange-100 hotwire-native:bg-red-100”></div>
用户可以在设备的系统设置中启用深色模式,这与您的应用完全无关。如果您不进行相应调整,可能会出现一些异常行为。
这两张截图显示的是启用了设备深色模式的应用。 左侧的 iOS 应用似乎缺少标题。而右侧的 Android 应用似乎没有返回按钮。
但它们确实存在!……严格来说。我们只是看不到它们,因为文字和图标的颜色与背景颜色太接近了。
使用媒体查询调整 CSS 以处理深色模式。这里,我们将深色模式启用时的背景颜色设置为深灰色,默认恢复为白色:
body {
background-color: #ffffff;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #121212;
}
}在 Android 系统中,不要忘记在 app/src/main/res/values-night/themes.xml 中将返回按钮的颜色设置为浅色,例如白色 :
<resources>
<style name="Base.Theme.AndroidDemo"
parent="Theme.Material3.DayNight.NoActionBar">
<item name="navigationIconTint">@color/white</item>
</style>
</resources>
以下是应用更改后的样子。深色模式:已启用!
iOS 和 Android 系统均支持深色模式。
对视口进行一些小调整<meta>标签可以让你的应用表现得更像真正的移动屏幕,而不是可缩放的网页。Rails 默认使用以浏览器为中心的设置,因此在 Hotwire Native 中运行时,我通常会添加一些覆盖代码:
<meta name="viewport"
content="width=device-width,initial-scale=1,user-scalable=no">
这些设置中的每一个都会改变一些细微但重要的方面:
width=device-width这样可以告诉网页视图,其布局宽度应与设备的宽度相匹配 ,而不是假装成缩小后的“桌面”页面。这能保持布局的可预测性,并防止大多数意外的水平滚动。
initial-scale=1确保页面以 1:1 的比例加载。否则,某些屏幕可能会根据设备以及网页视图缓存先前状态的方式而略微放大或缩小。这可以保证在来回导航时页面保持稳定。
user-scalable=no禁用捏合缩放功能,使您的应用更像原生屏幕。它还可以防止浏览器意外放大表单元素。
但这也移除了一项辅助功能: 依赖缩放功能阅读小字的用户将失去这项功能 。如果您使用此功能,请确保文本大小和点击目标足够大,以便这些用户能够轻松阅读。
以下是我用于本次深度分析的完整源代码。它包含一个 #Rails 应用以及 #iOS 和 #Android 项目,配置与此处所示完全一致。您可以随意将部分代码复制到自己的应用中,或者将其作为新项目的起点。
这些改进并非什么重大功能,而是一些细微却意义深远的决策,它们让 #Hotwire Native 应用显得更加用心 。一旦这些决策落实到位,你构建的一切都将建立在更加稳固的基础之上。Rails 渲染的网页视图不再像网站,而更像是真正的移动体验。
如果您有任何疑问或需要帮助将这些内容应用到您的应用程序中,请直接回复。我会阅读每一条回复!
不妨将此信息分享给其他开发者。这有助于让这份简报触达最需要它的人群。
Author Joe Masilotti
Source https://newsletter.masilotti.com/p/hotwire-native-deep-dive-native-polish
Share with your followers.
Reply