高级地点详情组件

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

高级地点详情配置

使用 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 slot 添加了内容。

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)
      }
    }
}