mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
Story locations
This commit is contained in:
@@ -35,10 +35,10 @@ private enum LocationPickerEntryId: Hashable {
|
||||
}
|
||||
|
||||
private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
case location(PresentationTheme, String, String, TelegramMediaMap?, CLLocationCoordinate2D?)
|
||||
case location(PresentationTheme, String, String, TelegramMediaMap?, Int64?, String?, CLLocationCoordinate2D?, String?)
|
||||
case liveLocation(PresentationTheme, String, String, CLLocationCoordinate2D?)
|
||||
case header(PresentationTheme, String)
|
||||
case venue(PresentationTheme, TelegramMediaMap?, Int)
|
||||
case venue(PresentationTheme, TelegramMediaMap?, Int64?, String?, Int)
|
||||
case attribution(PresentationTheme, LocationAttribution)
|
||||
|
||||
var stableId: LocationPickerEntryId {
|
||||
@@ -49,7 +49,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
return .liveLocation
|
||||
case .header:
|
||||
return .header
|
||||
case let .venue(_, venue, index):
|
||||
case let .venue(_, venue, _, _, index):
|
||||
return .venue(venue?.venue?.id ?? "\(index)")
|
||||
case .attribution:
|
||||
return .attribution
|
||||
@@ -58,8 +58,8 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
|
||||
static func ==(lhs: LocationPickerEntry, rhs: LocationPickerEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .location(lhsTheme, lhsTitle, lhsSubtitle, lhsVenue, lhsCoordinate):
|
||||
if case let .location(rhsTheme, rhsTitle, rhsSubtitle, rhsVenue, rhsCoordinate) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsCoordinate == rhsCoordinate {
|
||||
case let .location(lhsTheme, lhsTitle, lhsSubtitle, lhsVenue, lhsQueryId, lhsResultId, lhsCoordinate, lhsName):
|
||||
if case let .location(rhsTheme, rhsTitle, rhsSubtitle, rhsVenue, rhsQueryId, rhsResultId, rhsCoordinate, rhsName) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsQueryId == rhsQueryId && lhsResultId == rhsResultId, lhsCoordinate == rhsCoordinate, lhsName == rhsName {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -76,8 +76,8 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .venue(lhsTheme, lhsVenue, lhsIndex):
|
||||
if case let .venue(rhsTheme, rhsVenue, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsIndex == rhsIndex {
|
||||
case let .venue(lhsTheme, lhsVenue, lhsQueryId, lhsResultId, lhsIndex):
|
||||
if case let .venue(rhsTheme, rhsVenue, rhsQueryId, rhsResultId, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsQueryId == rhsQueryId && lhsResultId == rhsResultId, lhsIndex == rhsIndex {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -114,11 +114,11 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
case .venue, .attribution:
|
||||
return true
|
||||
}
|
||||
case let .venue(_, _, lhsIndex):
|
||||
case let .venue(_, _, _, _, lhsIndex):
|
||||
switch rhs {
|
||||
case .location, .liveLocation, .header:
|
||||
return false
|
||||
case let .venue(_, _, rhsIndex):
|
||||
case let .venue(_, _, _, _, rhsIndex):
|
||||
return lhsIndex < rhsIndex
|
||||
case .attribution:
|
||||
return true
|
||||
@@ -130,7 +130,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
|
||||
func item(engine: TelegramEngine, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> ListViewItem {
|
||||
switch self {
|
||||
case let .location(_, title, subtitle, venue, coordinate):
|
||||
case let .location(_, title, subtitle, venue, queryId, resultId, coordinate, name):
|
||||
let icon: LocationActionListItemIcon
|
||||
if let venue = venue {
|
||||
icon = .venue(venue)
|
||||
@@ -139,9 +139,9 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
}
|
||||
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: {
|
||||
if let venue = venue {
|
||||
interaction?.sendVenue(venue)
|
||||
interaction?.sendVenue(venue, queryId, resultId)
|
||||
} else if let coordinate = coordinate {
|
||||
interaction?.sendLocation(coordinate)
|
||||
interaction?.sendLocation(coordinate, name)
|
||||
}
|
||||
}, highlighted: { highlighted in
|
||||
interaction?.updateSendActionHighlight(highlighted)
|
||||
@@ -154,10 +154,10 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
||||
})
|
||||
case let .header(_, title):
|
||||
return LocationSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title)
|
||||
case let .venue(_, venue, _):
|
||||
case let .venue(_, venue, queryId, resultId, _):
|
||||
let venueType = venue?.venue?.type ?? ""
|
||||
return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), engine: engine, venue: venue, style: .plain, action: venue.flatMap { venue in
|
||||
return { interaction?.sendVenue(venue) }
|
||||
return { interaction?.sendVenue(venue, queryId, resultId) }
|
||||
}, infoAction: ["home", "work"].contains(venueType) ? {
|
||||
interaction?.openHomeWorkInfo()
|
||||
} : nil)
|
||||
@@ -181,7 +181,7 @@ enum LocationPickerLocation: Equatable {
|
||||
case none
|
||||
case selecting
|
||||
case location(CLLocationCoordinate2D, String?)
|
||||
case venue(TelegramMediaMap)
|
||||
case venue(TelegramMediaMap, Int64?, String?)
|
||||
|
||||
var isCustom: Bool {
|
||||
switch self {
|
||||
@@ -212,8 +212,8 @@ enum LocationPickerLocation: Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .venue(lhsVenue):
|
||||
if case let .venue(rhsVenue) = rhs, lhsVenue.venue?.id == rhsVenue.venue?.id {
|
||||
case let .venue(lhsVenue, lhsQueryId, lhsResultId):
|
||||
if case let .venue(rhsVenue, rhsQueryId, rhsResultId) = rhs, lhsVenue.venue?.id == rhsVenue.venue?.id, lhsQueryId == rhsQueryId, lhsResultId == rhsResultId {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -227,6 +227,7 @@ struct LocationPickerState {
|
||||
var mapMode: LocationMapMode
|
||||
var displayingMapModeOptions: Bool
|
||||
var selectedLocation: LocationPickerLocation
|
||||
var address: String?
|
||||
var forceSelection: Bool
|
||||
var searchingVenuesAround: Bool
|
||||
|
||||
@@ -234,6 +235,7 @@ struct LocationPickerState {
|
||||
self.mapMode = .map
|
||||
self.displayingMapModeOptions = false
|
||||
self.selectedLocation = .none
|
||||
self.address = nil
|
||||
self.forceSelection = false
|
||||
self.searchingVenuesAround = false
|
||||
}
|
||||
@@ -281,6 +283,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
private var presentationData: PresentationData
|
||||
private let presentationDataPromise: Promise<PresentationData>
|
||||
private let mode: LocationPickerMode
|
||||
private let source: LocationPickerController.Source
|
||||
private let interaction: LocationPickerInteraction
|
||||
private let locationManager: LocationManager
|
||||
|
||||
@@ -314,12 +317,13 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
var beganInteractiveDragging: () -> Void = {}
|
||||
var locationAccessDeniedUpdated: (Bool) -> Void = { _ in }
|
||||
|
||||
init(controller: LocationPickerController, context: AccountContext, presentationData: PresentationData, mode: LocationPickerMode, interaction: LocationPickerInteraction, locationManager: LocationManager) {
|
||||
init(controller: LocationPickerController, context: AccountContext, presentationData: PresentationData, mode: LocationPickerMode, source: LocationPickerController.Source, interaction: LocationPickerInteraction, locationManager: LocationManager) {
|
||||
self.controller = controller
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.presentationDataPromise = Promise(presentationData)
|
||||
self.mode = mode
|
||||
self.source = source
|
||||
self.interaction = interaction
|
||||
self.locationManager = locationManager
|
||||
|
||||
@@ -445,24 +449,37 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
}
|
||||
)
|
||||
|
||||
let venues: Signal<[TelegramMediaMap]?, NoError> = .single(nil)
|
||||
let venues: Signal<([(TelegramMediaMap, String)], Int64)?, NoError> = .single(nil)
|
||||
|> then(
|
||||
throttledUserLocation(userLocation)
|
||||
|> mapToSignal { location -> Signal<[TelegramMediaMap]?, NoError> in
|
||||
|> mapToSignal { location -> Signal<([(TelegramMediaMap, String)], Int64)?, NoError> in
|
||||
if let location = location, location.horizontalAccuracy > 0 {
|
||||
return combineLatest(nearbyVenues(context: context, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude), personalVenues)
|
||||
|> map { nearbyVenues, personalVenues -> [TelegramMediaMap]? in
|
||||
var resultVenues: [TelegramMediaMap] = []
|
||||
return combineLatest(nearbyVenues(context: context, story: source == .story, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude), personalVenues)
|
||||
|> map { contextResult, personalVenues -> ([(TelegramMediaMap, String)], Int64)? in
|
||||
var resultVenues: [(TelegramMediaMap, String)] = []
|
||||
if let personalVenues = personalVenues {
|
||||
for venue in personalVenues {
|
||||
let venueLocation = CLLocation(latitude: venue.latitude, longitude: venue.longitude)
|
||||
if venueLocation.distance(from: location) <= 1000 {
|
||||
resultVenues.append(venue)
|
||||
resultVenues.append((venue, ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
resultVenues.append(contentsOf: nearbyVenues)
|
||||
return resultVenues
|
||||
if let contextResult {
|
||||
for result in contextResult.results {
|
||||
switch result.message {
|
||||
case let .mapLocation(mapMedia, _):
|
||||
if let _ = mapMedia.venue {
|
||||
resultVenues.append((mapMedia, result.id))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return (resultVenues, contextResult.queryId)
|
||||
} else {
|
||||
return (resultVenues, 0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
@@ -470,17 +487,32 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
}
|
||||
)
|
||||
|
||||
let foundVenues: Signal<([TelegramMediaMap], CLLocation)?, NoError> = .single(nil)
|
||||
let foundVenues: Signal<([(TelegramMediaMap, String)], Int64, CLLocation)?, NoError> = .single(nil)
|
||||
|> then(
|
||||
self.searchVenuesPromise.get()
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { coordinate -> Signal<([TelegramMediaMap], CLLocation)?, NoError> in
|
||||
|> mapToSignal { coordinate -> Signal<([(TelegramMediaMap, String)], Int64, CLLocation)?, NoError> in
|
||||
if let coordinate = coordinate {
|
||||
return (.single(nil)
|
||||
|> then(
|
||||
nearbyVenues(context: context, latitude: coordinate.latitude, longitude: coordinate.longitude)
|
||||
|> map { venues -> ([TelegramMediaMap], CLLocation)? in
|
||||
return (venues, CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude))
|
||||
nearbyVenues(context: context, story: source == .story, latitude: coordinate.latitude, longitude: coordinate.longitude)
|
||||
|> map { contextResult -> ([(TelegramMediaMap, String)], Int64, CLLocation)? in
|
||||
if let contextResult {
|
||||
var resultVenues: [(TelegramMediaMap, String)] = []
|
||||
for result in contextResult.results {
|
||||
switch result.message {
|
||||
case let .mapLocation(mapMedia, _):
|
||||
if let _ = mapMedia.venue {
|
||||
resultVenues.append((mapMedia, result.id))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return (resultVenues, contextResult.queryId, CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
))
|
||||
} else {
|
||||
@@ -497,29 +529,37 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), userLocation, venues, foundVenues, self.locationContext.locationAccess())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData, state, userLocation, venues, foundVenuesAndLocation, access in
|
||||
if let strongSelf = self {
|
||||
let (foundVenues, foundVenuesLocation) = foundVenuesAndLocation ?? (nil, nil)
|
||||
|
||||
let (foundVenues, _, foundVenuesLocation) = foundVenuesAndLocation ?? (nil, nil, nil)
|
||||
|
||||
var entries: [LocationPickerEntry] = []
|
||||
switch state.selectedLocation {
|
||||
case let .location(coordinate, address):
|
||||
let title: String
|
||||
switch strongSelf.mode {
|
||||
case .share:
|
||||
if source == .story {
|
||||
title = "Add This Location"
|
||||
} else {
|
||||
title = presentationData.strings.Map_SendThisLocation
|
||||
}
|
||||
case .pick:
|
||||
title = presentationData.strings.Map_SetThisLocation
|
||||
}
|
||||
entries.append(.location(presentationData.theme, title, address ?? presentationData.strings.Map_Locating, nil, coordinate))
|
||||
entries.append(.location(presentationData.theme, title, address ?? presentationData.strings.Map_Locating, nil, nil, nil, coordinate, state.address))
|
||||
case .selecting:
|
||||
let title: String
|
||||
switch strongSelf.mode {
|
||||
case .share:
|
||||
if source == .story {
|
||||
title = "Add This Location"
|
||||
} else {
|
||||
title = presentationData.strings.Map_SendThisLocation
|
||||
}
|
||||
case .pick:
|
||||
title = presentationData.strings.Map_SetThisLocation
|
||||
}
|
||||
entries.append(.location(presentationData.theme, title, presentationData.strings.Map_Locating, nil, nil))
|
||||
case let .venue(venue):
|
||||
entries.append(.location(presentationData.theme, title, presentationData.strings.Map_Locating, nil, nil, nil, nil, nil))
|
||||
case let .venue(venue, queryId, resultId):
|
||||
let title: String
|
||||
switch strongSelf.mode {
|
||||
case .share:
|
||||
@@ -527,16 +567,20 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
case .pick:
|
||||
title = presentationData.strings.Map_SetThisPlace
|
||||
}
|
||||
entries.append(.location(presentationData.theme, title, venue.venue?.title ?? "", venue, venue.coordinate))
|
||||
entries.append(.location(presentationData.theme, title, venue.venue?.title ?? "", venue, queryId, resultId, venue.coordinate, nil))
|
||||
case .none:
|
||||
let title: String
|
||||
switch strongSelf.mode {
|
||||
case .share:
|
||||
if source == .story {
|
||||
title = "Add My Current Location"
|
||||
} else {
|
||||
title = presentationData.strings.Map_SendMyCurrentLocation
|
||||
}
|
||||
case .pick:
|
||||
title = presentationData.strings.Map_SetThisLocation
|
||||
}
|
||||
entries.append(.location(presentationData.theme, title, (userLocation?.horizontalAccuracy).flatMap { presentationData.strings.Map_AccurateTo(stringForDistance(strings: presentationData.strings, distance: $0)).string } ?? presentationData.strings.Map_Locating, nil, userLocation?.coordinate))
|
||||
entries.append(.location(presentationData.theme, title, (userLocation?.horizontalAccuracy).flatMap { presentationData.strings.Map_AccurateTo(stringForDistance(strings: presentationData.strings, distance: $0)).string } ?? presentationData.strings.Map_Locating, nil, nil, nil, userLocation?.coordinate, state.address))
|
||||
}
|
||||
|
||||
if case .share(_, _, true) = mode {
|
||||
@@ -545,17 +589,26 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
|
||||
entries.append(.header(presentationData.theme, presentationData.strings.Map_ChooseAPlace.uppercased()))
|
||||
|
||||
let displayedVenues = foundVenues != nil || state.searchingVenuesAround ? foundVenues : venues
|
||||
let displayedVenues: [(TelegramMediaMap, String)]?
|
||||
let queryId: Int64?
|
||||
if foundVenues != nil || state.searchingVenuesAround {
|
||||
displayedVenues = foundVenues
|
||||
queryId = foundVenuesAndLocation?.1
|
||||
} else {
|
||||
displayedVenues = venues?.0
|
||||
queryId = venues?.1
|
||||
}
|
||||
|
||||
var index: Int = 0
|
||||
if let venues = displayedVenues {
|
||||
if let venues = displayedVenues, let queryId {
|
||||
var attribution: LocationAttribution?
|
||||
for venue in venues {
|
||||
for (venue, resultId) in venues {
|
||||
if venue.venue?.provider == "foursquare" {
|
||||
attribution = .foursquare
|
||||
} else if venue.venue?.provider == "gplaces" {
|
||||
attribution = .google
|
||||
}
|
||||
entries.append(.venue(presentationData.theme, venue, index))
|
||||
entries.append(.venue(presentationData.theme, venue, queryId, resultId, index))
|
||||
index += 1
|
||||
}
|
||||
if let attribution = attribution {
|
||||
@@ -563,7 +616,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
}
|
||||
} else {
|
||||
for _ in 0 ..< 8 {
|
||||
entries.append(.venue(presentationData.theme, nil, index))
|
||||
entries.append(.venue(presentationData.theme, nil, nil, nil, index))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
@@ -615,15 +668,15 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .venue(venue):
|
||||
case let .venue(venue, _, _):
|
||||
strongSelf.headerNode.mapNode.setMapCenter(coordinate: venue.coordinate, hidePicker: true, animated: true)
|
||||
}
|
||||
|
||||
strongSelf.headerNode.updateState(mapMode: state.mapMode, trackingMode: .none, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: displayingPlacesButton, proximityNotification: nil, animated: true)
|
||||
|
||||
let annotations: [LocationPinAnnotation]
|
||||
if let venues = displayedVenues {
|
||||
annotations = venues.compactMap { LocationPinAnnotation(context: context, theme: presentationData.theme, location: $0) }
|
||||
if let venues = displayedVenues, let queryId {
|
||||
annotations = venues.compactMap { LocationPinAnnotation(context: context, theme: presentationData.theme, location: $0.0, queryId: queryId, resultId: $0.1) }
|
||||
} else {
|
||||
annotations = []
|
||||
}
|
||||
@@ -671,21 +724,43 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
if address.isEmpty {
|
||||
address = presentationData.strings.Map_Unknown
|
||||
}
|
||||
let name = placemark?.city ?? ""
|
||||
strongSelf.updateState { state in
|
||||
var state = state
|
||||
state.selectedLocation = .location(coordinate, address)
|
||||
state.address = name
|
||||
return state
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
strongSelf.geocodingDisposable.set(nil)
|
||||
if case .none = state.selectedLocation, let location = userLocation, state.address == nil {
|
||||
strongSelf.geocodingDisposable.set((reverseGeocodeLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] placemark in
|
||||
if let strongSelf = self {
|
||||
var address = placemark?.fullAddress ?? ""
|
||||
if address.isEmpty {
|
||||
address = presentationData.strings.Map_Unknown
|
||||
}
|
||||
let name = placemark?.city ?? ""
|
||||
strongSelf.updateState { state in
|
||||
var state = state
|
||||
state.address = name
|
||||
return state
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
strongSelf.geocodingDisposable.set(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if case let .share(_, selfPeer, _) = self.mode, let peer = selfPeer {
|
||||
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, peer: peer)
|
||||
if case let .share(_, selfPeer, _) = self.mode {
|
||||
if let selfPeer {
|
||||
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, peer: selfPeer)
|
||||
}
|
||||
self.headerNode.mapNode.hasPickerAnnotation = true
|
||||
}
|
||||
|
||||
@@ -747,7 +822,9 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
strongSelf.updateState { state in
|
||||
var state = state
|
||||
state.displayingMapModeOptions = false
|
||||
state.selectedLocation = annotation?.location.flatMap { .venue($0) } ?? .none
|
||||
if let annotation, let location = annotation.location {
|
||||
state.selectedLocation = .venue(location, annotation.queryId, annotation.resultId)
|
||||
}
|
||||
if annotation == nil {
|
||||
state.searchingVenuesAround = false
|
||||
}
|
||||
@@ -831,7 +908,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
return .complete()
|
||||
}
|
||||
|
||||
let searchContainerNode = LocationSearchContainerNode(context: self.context, coordinate: coordinate, interaction: self.interaction)
|
||||
let searchContainerNode = LocationSearchContainerNode(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, coordinate: coordinate, interaction: self.interaction, story: self.source == .story)
|
||||
self.insertSubnode(searchContainerNode, belowSubnode: navigationBar)
|
||||
self.searchContainerNode = searchContainerNode
|
||||
|
||||
|
||||
Reference in New Issue
Block a user