December 2019 - Swift 4 Tutorials W3Schools

Hot

Post Top Ad

16 Dec 2019

Create custom marker and custom info window in iOS using swift - Google maps

12/16/2019 09:29:00 am 10
In this article, we are going to learn about Custom Marker and Custom Info Window in Google maps.

First, create a new Xcode project and name it as Custom Marker.

In this example we are going to use completely auto layouts without a storyboard, every constraint is programmatically.

Download the sample project from the bottom of this tutorial.

Create custom marker and custom info window in iOS using swift - Google maps

Adding Google Map:

The first thing to do is integrate Google Maps SDK either using pod or other alternatives.

Here I am integrating using cocoapods.

Open terminal, go the project folder then type follow commands.

pod init

open -a xcode podfile

Then add pod 'GoogleMaps' below to the # Pods for CustomMarker.

pod install

After successful installation close the project and open workspace file.

To use google maps we need a key from google.

If you don't have one, get one from here

Okay, All set.

open AppDelegate.swift and import GoogleMaps then add the following line inside didFinishLaunchingWithOptions method:

GMSServices.provideAPIKey("YOUR API KEY")

open ViewController.swift and import GoogleMaps.

Add the following closure before viewDidLoad():

var mapView: GMSMapView = {
     let v = GMSMapView()
     v.translatesAutoresizingMaskIntoConstraints = false
     return v
 }()

Add mapView to the view as subview, add the following code inside viewDidLoad():

self.view.addSubview(mapView)
mapView.padding = UIEdgeInsets(top: 72, left: 25, bottom: 0, right: 25)
mapView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
mapView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
mapView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
mapView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
mapView.delegate = self

Don't panic with error, simply add the following extension to remove the error, we will discuss later in this example.

extension ViewController: GMSMapViewDelegate {
 
}

Now run the app, if your API key is valid you will see the map like below

Adding Google Map

Add Custom Marker:

Before creating a custom marker, first create our model, add the following model:

struct SSPlace {
    var name: String?
    var address: String?
    var coordinates: (lat: Double, lng: Double)?
}

Create array of SSPlaces as follow:

var places = [SSPlace]()

Add some sample data to places array, add the following code to the end of viewDidLoad():

places.append(SSPlace(name: "Fremont Troll", address: "N 36th St, Seattle, WA 98103, United States", coordinates: (lat: 47.651105, lng: -122.347347)))
places.append(SSPlace(name: "Grand Canyon National Park", address: "Arizona, United States", coordinates: (lat: 36.099982, lng: -112.123640)))
places.append(SSPlace(name: "Statue of Liberty National Monument", address: "New York, NY 10004, United States", coordinates: (lat: 40.689323, lng: -74.044490)))
places.append(SSPlace(name: "Yellowstone National Park", address: "United States", coordinates: (lat: 44.429311, lng: -110.588112)))
places.append(SSPlace(name: "Walt Disney World Resort", address: "Orlando, FL, United States", coordinates: (lat: 28.385280, lng: -81.563853)))

Now it's time to create a custom marker view.

Create a new swift file of type UIView and name it as CustomMarkerView. Replace the whole class with the following code:

class CustomMarkerView: UIView {
    
    var imageName: String?
    var borderColor: UIColor!
    
    init(frame: CGRect, imageName: String?, borderColor: UIColor, tag: Int) {
        super.init(frame: frame)
        self.imageName=imageName
        self.borderColor=borderColor
        self.tag = tag
        setupViews()
    }
    
    func setupViews() {
        let imgView = UIImageView()
        imgView.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(imgView)
        imgView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        imgView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        imgView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        imgView.heightAnchor.constraint(equalToConstant: 50).isActive = true
        imgView.layer.cornerRadius = 25
        imgView.layer.borderColor = borderColor?.cgColor
        imgView.contentMode = .scaleAspectFill
        imgView.layer.borderWidth = 4
        imgView.clipsToBounds = true
        imgView.image = UIImage(named: imageName!)
        
        let triangleImgView = UIImageView()
        self.insertSubview(triangleImgView, belowSubview: imgView)
        triangleImgView.translatesAutoresizingMaskIntoConstraints = false
        triangleImgView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        triangleImgView.topAnchor.constraint(equalTo: imgView.bottomAnchor, constant: -6).isActive = true
        triangleImgView.widthAnchor.constraint(equalToConstant: 23/2).isActive = true
        triangleImgView.heightAnchor.constraint(equalToConstant: 24/2).isActive = true
        triangleImgView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        triangleImgView.image = UIImage(named: "markerTriangle")
        triangleImgView.tintColor = borderColor
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

Here we are adding UIImageView to show the place image and one more UIImageView to show the bottom triangle.

Create new array to store all markers:

var markers = [GMSMarker]()

To add the places as markers on the map add the following method:

func addMarkers() {
    markers.removeAll()
    for (index, place) in places.enumerated() {
        let marker = GMSMarker()
        let customMarker = CustomMarkerView(frame: CGRect(x: 0, y: 0, width: customMarkerWidth, height: customMarkerHeight), imageName: place.name, borderColor: primaryColor, tag: index)
        marker.iconView = customMarker
        marker.position = CLLocationCoordinate2D(latitude: place.coordinates!.lat, longitude: place.coordinates!.lng)
        marker.infoWindowAnchor = CGPoint(x: 0.5, y: 0)
        marker.map = self.mapView
        marker.zIndex = Int32(index)
        marker.userData = place
        markers.append(marker)
    }
}

Add the following colors before ViewController class:

let primaryColor = UIColor(red:0.00, green:0.19, blue:0.56, alpha:1.0)

let secondaryColor = UIColor(red:0.89, green:0.15, blue:0.21, alpha:1.0)

After adding markers to the map let focus them so that users can look at those places only.

To focus the markers add the following methods:

func focusMapToShowAllMarkers() {
    if let firstLocation = markers.first?.position {
        var bounds =  GMSCoordinateBounds(coordinate: firstLocation, coordinate: firstLocation)
        
        for marker in markers {
            bounds = bounds.includingCoordinate(marker.position)
        }
        let update = GMSCameraUpdate.fit(bounds, withPadding: 20)
        self.mapView.animate(with: update)
    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    self.focusMapToShowAllMarkers()
}

Finally call the addMarkers() function at the end of viewDidLoad():

override func viewDidLoad() {
       super.viewDidLoad()
       /***
       self.addMarkers()
}

That's it run the app, you can see markers as follows:

Create custom marke in iOS using swift - Google maps

Add Custom Info Window:

Create a new swift file of type UIView and name it as CustomMarkerInfoWindow. Replace the whole class with the following code:

class CustomMarkerInfoWindow: UIView {
    
    var txtLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    var subtitleLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    var chevronButton: UIButton = {
        let v = UIButton()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    var imgView: UIImageView = {
        let v = UIImageView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = primaryColor
        self.addSubview(imgView)
        imgView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        imgView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        imgView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        imgView.widthAnchor.constraint(equalTo: imgView.heightAnchor, multiplier: 1).isActive = true
        imgView.heightAnchor.constraint(equalToConstant: 60).isActive = true
        imgView.clipsToBounds = true
        imgView.contentMode = .scaleAspectFill
        imgView.layer.cornerRadius = 5
        imgView.layer.masksToBounds = true
        imgView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
        
        self.addSubview(chevronButton)
        chevronButton.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
        chevronButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8).isActive = true
        chevronButton.widthAnchor.constraint(equalToConstant: 16).isActive = true
        chevronButton.heightAnchor.constraint(equalToConstant: 16).isActive = true
        chevronButton.setImage(UIImage(named: "chevron"), for: .normal)
        chevronButton.tintColor = UIColor.white
        chevronButton.isUserInteractionEnabled = false
        
        self.addSubview(txtLabel)
        txtLabel.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 4).isActive = true
        txtLabel.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 8).isActive = true
        txtLabel.trailingAnchor.constraint(equalTo: chevronButton.leadingAnchor, constant: -8).isActive = true
        txtLabel.bottomAnchor.constraint(greaterThanOrEqualTo: centerYAnchor, constant: 2).isActive = true
        txtLabel.font = UIFont.boldSystemFont(ofSize: 16)
        txtLabel.numberOfLines = 2
        txtLabel.textColor = UIColor.white
        txtLabel.text = "dfsdfd"
        
        self.addSubview(subtitleLabel)
        subtitleLabel.topAnchor.constraint(equalTo: txtLabel.bottomAnchor, constant: 0).isActive = true
        subtitleLabel.leadingAnchor.constraint(equalTo: txtLabel.leadingAnchor).isActive = true
        subtitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        subtitleLabel.font = UIFont.systemFont(ofSize: 14, weight: .light)
        subtitleLabel.textColor = UIColor.white
        subtitleLabel.text = "55656556"
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        layer.masksToBounds = true
        layer.cornerRadius = 5
    }   
}

Code looks big but that's a simple auto layout.

Okay, now whenever user taps on the marker we need to change the border color to red and show info window.

First to change the selected marker color to red add the following delegate method to the extension:

func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
    guard let customMarkerView = marker.iconView as? CustomMarkerView else { return false }
    let imgName = customMarkerView.imageName
    let customMarker = CustomMarkerView(frame: CGRect(x: 0, y: 0, width: customMarkerWidth, height: customMarkerHeight), imageName: imgName, borderColor: secondaryColor, tag: customMarkerView.tag)
    marker.iconView = customMarker
    return false
}

Then to show the Marker info window add the following delegate method:

func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
     if let place = marker.userData as? SSPlace {
         marker.tracksInfoWindowChanges = true
         let infoWindow = CustomMarkerInfoWindow()
         infoWindow.tag = 5555
         let height: CGFloat = 65
         let paddingWith = height + 16 + 32
         infoWindow.frame = CGRect(x: 0, y: 0, width: getEstimatedWidthForMarker(place, padding: paddingWith) + paddingWith, height: height)
         infoWindow.imgView.image = UIImage(named: place.name!)
         infoWindow.txtLabel.text = place.name
         infoWindow.subtitleLabel.text = place.address
         return infoWindow
     }
     return nil
 }
 
 func getEstimatedWidthForMarker(_ place: SSPlace, padding: CGFloat) -> CGFloat {
     var estimatedWidth: CGFloat = 0
     let infoWindow = CustomMarkerInfoWindow()
     let maxWidth = (UIDevice.current.userInterfaceIdiom == .pad ? UIScreen.main.bounds.width * 0.7 : UIScreen.main.bounds.width * 0.8) - padding
     let titleWidth = (place.name ?? "").width(withConstrainedHeight: infoWindow.txtLabel.frame.height, font: infoWindow.txtLabel.font)
     let subtitleWidth = (place.address ?? "").width(withConstrainedHeight: infoWindow.subtitleLabel.frame.height, font: infoWindow.subtitleLabel.font)
     estimatedWidth = min(maxWidth, max(titleWidth, subtitleWidth))
     return estimatedWidth
 }
 
 extension String {
     func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
         let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
         let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
         
         return ceil(boundingBox.width)
     }
 }

The getEstimatedWidthForMarker method is to calculate the window size based on the content.

Next after closing the info window you should change marker color is default blue, for that add the following delegate method:

func mapView(_ mapView: GMSMapView, didCloseInfoWindowOf marker: GMSMarker) {
    guard let customMarkerView = marker.iconView as? CustomMarkerView else { return }
    let imgName = customMarkerView.imageName
    let customMarker = CustomMarkerView(frame: CGRect(x: 0, y: 0, width: customMarkerWidth, height: customMarkerHeight), imageName: imgName, borderColor: primaryColor, tag: customMarkerView.tag)
    marker.iconView = customMarker
}

That's it all done, run the code you will see a map with markers then tap any one of the markers you will see the info window opened with marker color changed.

Create custom info window in iOS using swift - Google maps

This tutorial looks long, but you can directly download code or check in the Github.


Read More

11 Dec 2019

Chat App | Message Bubble in iOS Swift Without any external libraries.

12/11/2019 10:00:00 am 2
In this article we will learn how to create our own message kit like whatsapp, messanger using swift language.

We can customize the way we want like avatar, sender name, time label etc.


Chat App | Message Bubble in iOS Swift Without any external libraries.


At the end of this article you can find full project link.

Let's get started.

First create new single view application in xcode project.

Then add UITableView to the view and give constraints.

Create Custom UITableViewCell, this is important to create chat bubble.

Sender Chat Bubble:

Sender cell needs to have th message text and time.

The following method will do the magic.

func setupSendersCell() {
    let offset = UIEdgeInsets(top: padding, left: padding, bottom: -padding, right: -padding)
    self.contentView.addSubview(bgView)
    bgView.edges([.right, .top, .bottom], to: self.contentView, offset: offset)
    bgView.layer.cornerRadius = 8
    bgView.backgroundColor = UIColor(displayP3Red: 0, green: 122/255, blue: 255/255, alpha: 1)
    
    self.bgView.addSubview(textView)
    textView.edges([.left, .right, .top], to: self.bgView, offset: .init(top: innerSpacing, left: innerSpacing, bottom: -innerSpacing, right: -innerSpacing))
    bgView.leadingAnchor.constraint(greaterThanOrEqualTo: self.contentView.leadingAnchor, constant: extraSpacing).isActive = true
    textView.isScrollEnabled = false
    textView.isEditable = false
    textView.isSelectable = true
    textView.font = UIFont.systemFont(ofSize: 14)
    textView.textColor = UIColor.white
    textView.text = "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum"
    textView.backgroundColor = UIColor.clear
    
    self.bgView.addSubview(bottomLabel)
    bottomLabel.edges([.left, .bottom], to: self.bgView, offset: UIEdgeInsets(top: innerSpacing, left: secondaryPadding, bottom: -secondaryPadding, right: 0))
    bottomLabel.trailingAnchor.constraint(equalTo: textView.trailingAnchor, constant: -secondaryPadding).isActive = true
    bottomLabel.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: -2).isActive = true
    bottomLabel.font = UIFont.systemFont(ofSize: 10)
    bottomLabel.textColor = UIColor.white
    bottomLabel.textAlignment = .right
    bottomLabel.text = "12:00 AM"
}

Receiver Chat Bubble:

Receiver cell same as sender cell but it subviews should algin to leading where sender cell subviews align to trailing of superview.

The following method will do the magic.

func setupReceiversCell() {
    let offset = UIEdgeInsets(top: padding, left: padding, bottom: -padding, right: -padding)
    self.contentView.addSubview(bgView)
    bgView.edges([.left, .top, .bottom], to: self.contentView, offset: offset)
    bgView.layer.cornerRadius = 8
    bgView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.1)
    
    self.bgView.addSubview(topLabel)
    topLabel.edges([.left, .top], to: self.bgView, offset: UIEdgeInsets(top: secondaryPadding, left: secondaryPadding, bottom: 0, right: 0))
    topLabel.font = UIFont.boldSystemFont(ofSize: 14)
    topLabel.textColor = UIColor.red
    topLabel.text = "Red"
    
    self.bgView.addSubview(textView)
    textviewTopConstraintToTopLabel = textView.topAnchor.constraint(equalTo: topLabel.bottomAnchor, constant: 0)
    textviewTopConstraintToTopLabel.isActive = true
    textviewTopConstraintToBg = textView.topAnchor.constraint(equalTo: bgView.topAnchor, constant: innerSpacing)
    textviewTopConstraintToBg.isActive = false
    textView.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: innerSpacing).isActive = true
    textView.trailingAnchor.constraint(equalTo: bgView.trailingAnchor, constant: -innerSpacing).isActive = true
    topLabel.trailingAnchor.constraint(lessThanOrEqualTo: textView.trailingAnchor, constant: 0).isActive = true
    bgView.trailingAnchor.constraint(lessThanOrEqualTo: self.contentView.trailingAnchor, constant: -extraSpacing).isActive = true
    textView.isScrollEnabled = false
    textView.isEditable = false
    textView.isSelectable = true
    textView.font = UIFont.systemFont(ofSize: 14)
    textView.text = "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum"
    textView.backgroundColor = UIColor.clear
    
    self.bgView.addSubview(bottomLabel)
    bottomLabel.edges([.left, .bottom], to: self.bgView, offset: UIEdgeInsets(top: innerSpacing, left: secondaryPadding, bottom: -secondaryPadding, right: 0))
    bottomLabel.trailingAnchor.constraint(equalTo: textView.trailingAnchor, constant: -secondaryPadding).isActive = true
    bottomLabel.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: -2).isActive = true
    bottomLabel.font = UIFont.systemFont(ofSize: 10)
    bottomLabel.textColor = UIColor.lightGray
    bottomLabel.textAlignment = .right
    bottomLabel.text = "12:00 AM"
}

Add the two cells to tableview and give some random messages.

Input TextView:

Adding texview is little bit tricky, here i created input textview like whatsapp.

The send button will show up when ever some text is there in textbox.

Finally project will looks like the following




Download sample project with example :

Read More

10 Dec 2019

Firebase Dynamic Link Not Found in swift ios

12/10/2019 04:50:00 am 0
Failed to resolve uri domain prefix: https://www.yourdomain.com.

If you are the developer of this app, ensure that your Dynamic Links domain is correctly configured and that the path component of this URL is valid.

If you are the developer of this app, ensure that your Dynamic Links domain is correctly configured and that the path component of this URL is valid.

Possible reasons for the invalid dynamic link error:

Reason 1:

First thing you need to check with apple-app-site-association as follow

paste your domain with apple-app-site-association https://www.yourdomain.com/apple-app-site-association.

Expected output should be

{"applinks":{"apps":[],"details":[{"appID":"LQABBB9F84.com.yourdomain.YourDomain","paths":["NOT /_/*","/*"]}]}}

Incase if you are getting `Dynamic Link Not Found` means check the following two things:

1. App Store ID is valid or not
2. Team ID should be same the one in provising profile

Reason 2:

Second possbile reason might be in capabilities.

Enable assocaited domains and add your domain as applinks:www.yourdomain.com.

Here make sure that you need to remove https://, otherwise deeplink url will not work as expected.

Reason 3:

If you are using any custom domains that means you are not using domains that ends with page.link, you need to add FirebaseDynamicLinksCustomDomains key in info.plist.

<key>FirebaseDynamicLinksCustomDomains</key>
<array>
    <string>https://www.yourdomain.com/test1</string>
    <string>https://www.yourdomain.com/test2</string>
</array>


Not required if you are using domains with page.link.

Reason 4:

On some iOS devices it always goes to browser, but the same link will work on other ios devices. This is because of safari caching.

Go to settings in iOS device, then tap on safari and open reader, then tap on edit select preview.page.link and delete.

Go to settings in iOS device, then tap on safari and open reader, then tap on edit select preview.page.link and delete.

That's it should work fine.

Tip: You can try pasting the url in notes app and long press on the link then tap on `open in your app'

Read More

Post Top Ad