“高级地点详情”组件

“地点详情”组件的高级版提供与地点详情基本功能版相同的功能。您还可以选择是否显示地点名称、配置组件的操作按钮,以及对显示的评价和媒体应用搜索和过滤选项。

高级地点详情配置

使用 AdvancedPlaceDetailsView 或 AdvancedPlaceDetailsCompactView 访问高级功能。

内容显示选项

您可以使用 AdvancedPlaceDetailsContent 中的枚举来指定要显示的内容。您可以配置与地点详情基本信息相同的所有内容,还可以选择是否显示地点名称。

配置操作按钮

使用 AdvancedPlaceDetailsView 或 AdvancedPlaceDetailsCompactView 的 .cornerActions 和 .mainActions 方法移除或自定义操作按钮。

视觉必须提供出处要求

包含自定义按钮的地点详情紧凑视图
带有自定义按钮的高级地点详情紧凑视图

重要提示:如果自定义操作按钮会与 Google 以外的第三方分享用户信息,您必须向最终用户明确说明这一点。请参阅标明出处要求。

右侧示例展示了一张自定义图片,该图片清楚地表明用户信息将发送给“Altostrat”。

应用搜索和过滤选项

使用 AdvancedPlaceDetailsView 或 AdvancedPlaceDetailsCompactView 的 .searchMediaOptions 和 .searchReviewOptions 方法,为媒体或评价启用搜索和过滤功能。

创建自定义内容槽

自定义内容槽展示位置选项

借助自定义内容槽,您可以将自己的内容(例如 AI 生成的摘要、行程时间戳或自定义标记)直接添加到预定义的布局区域中。

如需添加自定义内容,请使用 AdvancedPlaceDetailsCompactView 的 .placeDetailsCompactContentSlot 方法。可用的位置包括 .basicSectionTop 和 .basicSectionBottom。

内容槽的提供方信息要求和内容建议

装载自定义内容槽后,披露声明会自动更新,以阐明哪些内容由 Google 提供,以及哪些内容由第三方提供。

正确做法

在 Google 地点详情和自定义内容之间保持清晰的视觉层次结构,以避免信息来源出现任何歧义。

避免信息来源不明确

正确做法

考虑提供本地化的、高价值的分析洞见,以契合用户的即时目标。提供量身定制的内容(例如实时预订窗口或当前空房情况)有助于用户更快做出更明智的决策。

提供高价值的数据洞见

错误做法

请勿复制现有的 Google 地点详情,例如评分、评价或营业时间,即使您已隐藏 Google 提供的内容。这样做可能会让用户对内容的来源感到困惑,不确定内容是来自 Google 还是第三方。

请勿复制现有的 Google 地点详情

错误做法

请勿展示可能被误认为 Google 官方认可的无来源声明或徽章(例如“首要推荐!”)。自定义内容必须明确注明其来源。

请勿展示未注明出处的声明或徽章

错误做法

请勿在自定义地点容器中添加广告、营销文案、促销折扣或订阅提示。

不得包含广告、营销文案、促销折扣或订阅提示

高级地点详情示例

省略地点名称

如需省略地点名称,请勿在 AdvancedPlaceDetailsConfiguration 的 content 参数中添加 .name。

Swift

struct DemoView: View {
  static let peetsCoffeePlaceID = "ChIJT7FdmYiAhYAROFOvrIxRJDU"

  // Query for loading the place details component.
  @State var query: PlaceDetailsQuery = PlaceDetailsQuery(
    identifier: .placeID(peetsCoffeePlaceID)
  )
  @State private var favoritePlaces: Set<String> = []

  var body: some View {
    AdvancedPlaceDetailsView(
      query: $query,
      configuration: AdvancedPlaceDetailsConfiguration(
        content:[.address()],
        theme: PlacesMaterialTheme()
      ),
      placeDetailsCallback: { result in
        if let place = result.place {
          print("Place: \(place.description)")
        } else {
          print("Error: \(String(describing: result.error))")
        }
      }
    )
  }
}

移除操作按钮

Swift

struct DemoView: View {
  static let peetsCoffeePlaceID = "ChIJT7FdmYiAhYAROFOvrIxRJDU"

  @State var query: PlaceDetailsQuery = PlaceDetailsQuery(
    identifier: .placeID(peetsCoffeePlaceID)
  )

  var body: some View {
    AdvancedPlaceDetailsView(
      query: $query,
      configuration: AdvancedPlaceDetailsConfiguration(
        content: AdvancedPlaceDetailsView.allContent,
        theme: PlacesMaterialTheme()
      ),
      placeDetailsCallback: { result in
        if let place = result.place {
          print("Place: \(place.description)")
        } else {
          print("Error: \(String(describing: result.error))")
        }
      }
    )
    .mainActions{ _ in
      return []
    }
  }
}

添加自定义操作和预定义操作

此示例添加了一个选项,供用户收藏或取消收藏某个地点,以及预定义的在 Google 地图中打开操作。

Swift

struct DemoView: View {
  static let peetsCoffeePlaceID = "ChIJT7FdmYiAhYAROFOvrIxRJDU"

  @State var query: PlaceDetailsQuery = PlaceDetailsQuery(
    identifier: .placeID(peetsCoffeePlaceID)
  )
  
  @State var favoritePlaces: Set<String> = []

  var body: some View {
    AdvancedPlaceDetailsView(
      query: $query,
      configuration: AdvancedPlaceDetailsConfiguration(
        content: AdvancedPlaceDetailsView.allContent,
        theme: PlacesMaterialTheme()
      ),
      placeDetailsCallback: { result in
        if let place = result.place {
          print("Place: \(place.description)")
        } else {
          print("Error: \(String(describing: result.error))")
        }
      }
    )
    .mainActions{ place in
      var actions: [MainPlaceActionElement] = []
      let placeID = place.placeID!
      if self.favoritePlaces.contains(placeID) {
        actions.append(
          .action(
            MainPlaceAction(
              image: Image(systemName: "heart.fill"),
              label: "Unfavorite"
            ) { _ in self.toggleFavorite(placeID) }
          )
        )
      } else {
        actions.append(
          .action(
            MainPlaceAction(
              image: Image(systemName: "heart"),
              label: "Favorite"
            ) { _ in self.toggleFavorite(placeID) }
          )
        )
      }
      actions.append(.actionId(.openMap(style: .primary)))
      return actions
    }
    .cornerActions { place in
      var actions: [CornerPlaceActionElement] = []
      let placeID = place.placeID!
      if self.favoritePlaces.contains(placeID) {
        actions.append(
          .action(
            CornerPlaceAction(
              image: Image(systemName: "heart.fill"),
              accessibilityLabel: "Unfavorite",
            ) { _ in self.toggleFavorite(placeID) }
          )
        )
      } else {
        actions.append(
          .action(
            CornerPlaceAction(
              image: Image(systemName: "heart"),
              accessibilityLabel: "Favorite",
            ) { _ in self.toggleFavorite(placeID) }
          )
        )
      }
      actions.append(.actionId(.openMap(style: .primary)))
      return actions
    }
  }

  func toggleFavorite(_ placeID: String) {
    if favoritePlaces.contains(placeID) {
      favoritePlaces.remove(placeID)
    } else {
      favoritePlaces.insert(placeID)
    }
  }
}

过滤评价和媒体内容

此代码示例仅显示提及“咖啡”的媒体和评价。

Swift

struct DemoView: View {
  static let peetsCoffeePlaceID = "ChIJT7FdmYiAhYAROFOvrIxRJDU"

  @State var query: PlaceDetailsQuery = PlaceDetailsQuery(
    identifier: .placeID(peetsCoffeePlaceID)
  )
  let searchMediaOptions = SearchMediaOptions(query: "coffee")
  let searchReviewsOptions = SearchReviewsOptions(query: "coffee")

  var body: some View {
    AdvancedPlaceDetailsView(
      query: $query,
      configuration: AdvancedPlaceDetailsConfiguration(
        content: [.media(), .reviews()],
        theme: PlacesMaterialTheme()
      ),
      placeDetailsCallback: { _ in }
    )
    .searchMediaOptions(searchMediaOptions) { results in
      guard let result = results.first else { return }
      switch result {
      case .success(let summary):
        print("Summary: \(summary)")
      case .failure(let error):
        print("Error: \(error)")
      }
    }
    .searchReviewsOptions(searchReviewsOptions) { results in
      guard let result = results.first else { return }
      switch result {
      case .success(let summary):
        print("Summary: \(summary)")
      case .failure(let error):
        print("Error: \(error)")
      }
    }
  }
}

创建内容槽

此示例向 basicSectionTop 和 basicSectionBottom 槽添加了内容。

Swift

import SwiftUI
import PlacesSwift

struct ItineraryCard: View {
    static let peetsCoffeePlaceID = "ChIJT7FdmYiAhYAROFOvrIxRJDU"

    @State var query: PlaceDetailsQuery = PlaceDetailsQuery(
      identifier: .placeID(peetsCoffeePlaceID)
    )

    var body: some View {
      AdvancedPlaceDetailsCompactView(
        orientation: .vertical,
        query: $query,
        configuration: AdvancedPlaceDetailsCompactConfiguration(
          content: AdvancedPlaceDetailsCompactView.allContent,
          theme: PlacesMaterialTheme()
        ),
        placeDetailsCallback: { _ in }
      )
      .placeDetailsCompactContentSlot(position: .basicSectionTop) { place in
        Text("Day 2 • 2:30 PM")
          .font(.caption)
          .fontWeight(.bold)
          .foregroundColor(.accentColor)
      }
      .placeDetailsCompactContentSlot(position: .basicSectionBottom) { place in
        VStack(alignment: .leading, spacing: 4) {
          Text("AI Notes")
            .font(.caption)
            .bold()
          Text("Great quiet atmosphere for morning meetings. Famous for house-roasted pour-overs.")
            .font(.footnote)
            .foregroundColor(.secondary)
        }
        .padding(8)
        .background(Color(.systemGray6))
        .cornerRadius(8)
      }
    }
}