The default MapMarker hides information that is under it, for example a city name. If zoomed closed enough the marker doesn't cover the name anymore and the name is displayed. When using a custom annotation view both the city name and the annotation is displayed, it looks a lil messy. Is it possible to get the behaviour from MapMarker when using custom annotations?
MapMarker: https://i.stack.imgur.com/OhQxj.png
Custom: https://i.stack.imgur.com/GwO23.png
Maybe something with clustering or collisions, but seems do be annotations colliding with annotations.
setting the value of .displayPriority to some value seems to do the trick. Don't know which value to set though.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotation = MKAnnotationView(annotation: annotation, reuseIdentifier: "annotation")
annotation.image = UIImage(systemName: "circle.fill")
annotation.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
annotation.displayPriority = .defaultHigh
return annotation
}
Related
I am showing a UIViewController via a SwiftUI view, like so:
#available(iOS 13, *)
struct WelcomeNavView: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<WelcomeNavView>) -> UINavigationController {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVc = storyboard.instantiateViewController(withIdentifier: "welcomeNav") as! UINavigationController
return navVc
}
func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext<WelcomeNavView>) {
}
}
I then present it from a ViewController like so:
self.viewController?.present(style: .fullScreen) {
WelcomeNavView()
}
However, it does not occupy the entire screen and the UIHostViewController color is showing at the top and bottom:
How can I change the color of the UIHostingViewController's view.. Or expand the View it is holding to occupy the entire screen?
Another simple and quick solution is, you can ignore safe area of your WelcomeNavView() while presenting.
example
iOS 13:
WelcomeNavView().edgesIgnoringSafeArea(.all)
iOS 14 and above:
WelcomeNavView().ignoresSafeArea()
Here is the solution,
I ran into similar problem, wherein I had to set hosting view controller's background clear.
Apparently, rootView (probably our SwiftUI View) of hostingVC and hostingVC.view are different.
so this would allow you to change to background color.
hostingVC.view.backgroundColor = .clear
Of course use any color in place of ".clear".
Just keep in mind to change color of SwiftUI view passed as rootView to hostingVC accordingly, mostly make that clear, so it won't be shown against above set color.
Your_SwiftUI_View.background(Color.clear)
I'm looking at an example of using SwiftUI with Combine: MVVM with Combine Tutorial for iOS at raywenderlich.com. A ViewModel implementation is given like this:
class WeeklyWeatherViewModel: ObservableObject, Identifiable {
// 2
#Published var city: String = ""
// 3
#Published var dataSource: [DailyWeatherRowViewModel] = []
private let weatherFetcher: WeatherFetchable
// 4
private var disposables = Set<AnyCancellable>()
init(weatherFetcher: WeatherFetchable) {
self.weatherFetcher = weatherFetcher
}
}
So, this makes some sense to me. In a view observing the model, an instance of the ViewModel is declared as an ObservedObject like this:
#ObservedObject var viewModel: WeeklyWeatherViewModel
And then it's possible to make use of the #Published properties in the model in the body definition of the View like this:
TextField("e.g. Cupertino", text: $viewModel.city)
In WeeklyWeatherViewModel Combine is used to take the city text, make a request on it, and turn this in to [DailyWeatherRowViewModel]. Up to here, everything is rosey and makes sense.
Where I become confused is that quite a lot of code is then used to:
Trigger a fetch when city is changed.
Keep hold of the AnyCancellable that's looking up weather data.
Copy the output of the weather look up in to dataSource by a sink on the weather fetch Publisher`
It looks like this:
// More in WeeklyWeatherViewModel
init(
weatherFetcher: WeatherFetchable,
scheduler: DispatchQueue = DispatchQueue(label: "WeatherViewModel")
) {
self.weatherFetcher = weatherFetcher
_ = $city
.dropFirst(1)
.debounce(for: .seconds(0.5), scheduler: scheduler)
.sink(receiveValue: fetchWeather(forCity:))
}
func fetchWeather(forCity city: String) {
weatherFetcher.weeklyWeatherForecast(forCity: city)
.map { response in
response.list.map(DailyWeatherRowViewModel.init)
}
.map(Array.removeDuplicates)
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [weak self] value in
guard let self = self else { return }
switch value {
case .failure:
self.dataSource = []
case .finished:
break
}
},
receiveValue: { [weak self] forecast in
guard let self = self else { return }
self.dataSource = forecast
})
.store(in: &disposables)
}
If I look in Combine for the definition of the #Published propertyWrapper, it seems like all does is provide projectedValue which is a Publisher, which makes it seem like it ought to be possible for WeeklyWeatherViewModel to simply provide the Publisher fetching weather data and for the view to make use of this directly. I don't see why the copying in to a dataSource is necessary.
Basically, what I'm expecting is there to be a way for SwiftUI to directly make use of a Publisher, and for me to be able to put that publisher externally from a View implementation so that I can inject it. But I've no idea what it is.
If this doesn't seem to make any sense, that figures, as I'm confused. Please let me know and I'll see if I can refine my explanation. Thanks!
I don't have a definitive answer to this and I didn't find a magic way to have SwiftUI make use of a Publisher directly – it's entirely possible that there is one that eludes me!
I have found a reasonably compact and flexible approach to achieving the desired result, though. It cut down the use of sink to a single occurrence that attaches to the input (#Published city in the original code), which substantially simplifies the cancelation work.
Here's a fairly generic model that has an #Published input attribute and a #Published output attribute (for which setting is private). It takes a transform as input, and this is used to transform the input publisher, and is then sinked in to the output publisher. The Cancelable of the sink is stored.
final class ObservablePublisher<Input, Output>: ObservableObject, Identifiable {
init(
initialInput: Input,
initialOutput: Output,
publisherTransform: #escaping (AnyPublisher<Input, Never>) -> AnyPublisher<Output, Never>)
{
input = initialInput
output = initialOutput
sinkCancelable =
publisherTransform($input.eraseToAnyPublisher())
.receive(on: DispatchQueue.main)
.sink(receiveValue: { self.output = $0 })
}
#Published var input: Input
#Published private(set) var output: Output
private var sinkCancelable: AnyCancellable? = nil
}
If you wanted a substantially less generic kind of model, you can see it's pretty easy to set up having the input (which is a publisher) be filtered in to the output.
In a view, you might declare an instance of the model and use it like this:
struct SimpleView: View {
#ObservedObject var model: ObservablePublisher<String, String>
var body: some View {
List {
Section {
// Here's the input to the model taken froma text field.
TextField("Give me some input", text: $model.input)
}
Section {
// Here's the models output which the model is getting from a passed Publisher.
Text(model.output)
}
}
.listStyle(GroupedListStyle())
}
}
And here's some silly setup of the view and its model taken from a "SceneDelegate.swift". The model just delays whatever is typed in for a bit.
let model = ObservablePublisher(initialInput: "Moo moo", initialOutput: []) { textPublisher in
return textPublisher
.delay(for: 1, scheduler: DispatchQueue.global())
.eraseToAnyPublisher()
}
let rootView = NavigationView {
AlbumSearchView(model: model)
}
I made the model generic on the Input and Output. I don't know if this will actually be useful in practice, but it seems like it might be.
I'm really new to this, and there might be some terrible flaws in this such as inefficiencies, memory leaks or retain cycles, race conditions, etc. I've not found them yet, though.
You can use URLSessionDataTaskPublisher and refactor out networking from all view models.
If you feel some part of the tutorial seems redundant, that is because it is.
MVVM in such usage is redundant and does not do the job better.
I have a refactored version (networking refactored, all view models removed) of that tutorial if you are interested in details.
I have designed a UIStackView in a StoryBoard and I have added 2 UIbutton with a height contraint set at 70.
My stackview is set to vertical fill and has some constraints set on the storyboard to fit in the Controller's View.
I have added an outlet from the stackView and from the UIbutton.
I am trying to add a third UIButton programatically in the stack View
#IBOutlet weak var Btn1: UIButton!
#IBOutlet weak var myStackView: UIStackView!
var accounts = [Account]()
override func viewDidLoad() {
super.viewDidLoad()
Btn1.setTitle("Name", for: .normal)
let Btn3 = UIButton(frame: CGRect(x: 0, y: 110, width: 100, height: 50))
Btn3.setTitle("Btn3", for: .normal)
Btn3.heightAnchor.constraint(equalToConstant: 70).isActive = true
myStackView.addArrangedSubview(Btn3)
But Btn3 never appears
Well, assuming that your background color is default-white, the button is there, it's just hard to see ;). In other words: its title color is white.
Try setting some colors:
Btn3.backgroundColor = UIColor.yellow
Btn3.setTitleColor(UIColor.red, for: .normal)
You could also create it as a system button if you care about tintColor:
let Btn3 = UIButton(type: .system)
If you create a button using init(frame:), you get a custom button and the tintColor documentation has this to say:
This property has no default effect for buttons with type custom. For
custom buttons, you must implement any behavior related to tintColor
yourself.
Also, rather than specifying a meaningless frame (layout is managed by the stack view anyway) when initializing the button using init(frame:), I'd suggest using .zero:
let Btn3 = UIButton(frame: .zero)
And a final note regarding Swift naming conventions:
Follow case conventions. Names of types and protocols are
UpperCamelCase. Everything else is lowerCamelCase.
So Btn3 suggests a type, while btn3 or loginButton suggests a variable. See "Follow case conventions" in the Swift API Design Guidelines.
I just try to extend my height of navigation bar...I tried that code below, Its working fine....but it can't able to show the title? I tried self.title ="" and self.navigationitem.title ="" also......
self.navBar.tintColor = UIColor.blue
// self.navigationItem.title = "Instrumental"
self.title = "Instrumental"
navBar.titleTextAttributes = [NSForegroundColorAttributeName:UIColor.white]
let backButton = UIButton(type: .custom)
backButton.frame = CGRect(x: 380, y: 15, width: 30, height: 30)
backButton.setImage(UIImage(named:"vertical-dots (1)"), for: .normal)
// backButton.addTarget(self, action: #selector(ViewController.backButton(_:)), for: .TouchUpInside)
navBar.addSubview(backButton)
func setNavBarToTheView() {
self.navBar.frame = CGRect(x:0, y:0, width:420, height:60) // Here you can set you Width and Height for your navBar
self.navBar.backgroundColor = (UIColor.blue)
self.view.addSubview(navBar)
}
You can add title programmatically for navigation bar like this : -
Swift 3
override func viewDidLoad() {
super.viewDidLoad()
self.title = "First View"
}
It appears that you are manually adding a UINavBar to the view. When you do that you lose all of the automatic features when you put your view controller into a navigation controller.
To set the title of your nav bar you need to also create a UINavigationItem and set it to the nav bar's items property.
Things really would be a lot simpler if you put your view controller in a navigation controller. Then you get all of the default behavior including a standard nav bar without the need to do all the work of adding your own.
How can I access the pins on an MKMapView via XCTest's UI Tests?
I want to count the number, verify specific ones are there (based on accessibility id or title), etc.
There doesn't seem to be a XCUIElementType for MKAnnotation.
I'm having a hard time finding any documentation on MKMapView + XCTest.
The annotation views aren't under maps unfortunately. You will find map points of interest there instead. To query for an annotation view you should use XCUIApplication().windows.element.otherElements["Custom Identifier"].
Add the accessibilityIdentifier = "Custom Identifier" to your annotation view, not annotation. MKAnnotation doesn't implement accessibilityIdentifier.
Use UIAccessibilityIdentification.
class Annotation: NSObject, MKAnnotation, UIAccessibilityIdentification {
let coordinate: CLLocationCoordinate2D
let title: String?
var accessibilityIdentifier: String?
init(title: String?, coordinate: CLLocationCoordinate2D) {
self.title = title
self.coordinate = coordinate
}
}
let coordinate = CLLocationCoordinate2DMake(40.2853, -73.3382)
let annotation = Annotation(title: "A Place Title", coordinate: coordinate)
annotation.accessibilityIdentifier = "Some Identifier"
let mapView = MKMapView()
mapView.addAnnotation(annotation)
Under test you can reference the annotation via otherElements.
let app = XCUIApplication()
let annotation = app.maps.element.otherElements["Custom Identifier"]
annotation.tap()
In my map i had only 1 pin and i could access to it with Map pin identifier like this:
let annotation = app.otherElements.matching(identifier: "Map pin").firstMatch
XCTAssertTrue(annotation.exists)
annotation.tap()