Swift Newbie here, trying to learn the code. I have a javascript background though. I've designed a simple interface using nested stackviews in the storyboard:
The hierarchy is
Vertical Stack
-Horizontal Stack
-Vertical Stack
Label
Textfield
-Vertical Stack
Label
TextField
I've basically set up a table like display, that i want to replicate everytime a user clicks the "Add Member button" It needs to duplicate what's there and add it top stack. Add a New horizontal stack composed of two vertical stacks with the label and text fields.
When I press the button nothing happens. No errors either. I used a print to confirm the button event is firing.
here is the controller code:
class createCrewController: UIViewController{
//MARK: Properties
var memberList:UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
memberList = view.viewWithTag(1) as! UIStackView!;
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var rowArray = [UIStackView]()
//MARK: Actions
#IBAction func addMemberBTN(_ sender: UIButton) {
//Create name and TextField
let nameLabel = UILabel();
nameLabel.text = "Name";
let nameTextField = UITextField();
nameTextField.placeholder = "Enter Name";
let phoneLabel = UILabel();
phoneLabel.text = "Name";
let phoneTextField = UITextField();
phoneTextField.placeholder = "Enter Phone";
//Insert them into vertical Stack
let nameRow = UIStackView(arrangedSubviews: [nameLabel,nameTextField]);
nameRow.axis = .vertical;
let phoneRow = UIStackView(arrangedSubviews: [phoneLabel,phoneTextField]);
phoneRow.axis = .vertical;
let columnView = UIStackView(arrangedSubviews:[nameRow,phoneRow]);
columnView.axis = .horizontal;
self.memberList.addSubview(columnView);
}
Related
My app requests JSON data (latitude, longitude, and other information about a place) and then displays them on a map in a form of clickable annotations. I'm receiving around 30,000 of those, so as you can imagine, the app can get a little "laggy".
The solution I think would fit the app best is to show those annotations only on a certain zoom level (for example when the user zooms so only one city is visible at once, the annotations will show up). Since there's a lot of them, showing all 30,000 would probably crash the app, that's why I also aim at showing just those that are close to where the user zoomed in.
The code below shows immediately all annotations at once at all zoom levels. Is there a way to adapt it to do the things I described above?
struct Map: UIViewRepresentable {
#EnvironmentObject var model: ContentModel
#ObservedObject var data = FetchData()
var locations:[MKPointAnnotation] {
var annotations = [MKPointAnnotation]()
// Loop through all places
for place in data.dataList {
// If the place does have lat and long, create an annotation
if let lat = place.latitude, let long = place.longitude {
// Create an annotation
let a = MKPointAnnotation()
a.coordinate = CLLocationCoordinate2D(latitude: Double(lat)!, longitude: Double(long)!)
a.title = place.address ?? ""
annotations.append(a)
}
}
return annotations
}
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
// Show user on the map
mapView.showsUserLocation = true
mapView.userTrackingMode = .followWithHeading
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
// Remove all annotations
uiView.removeAnnotations(uiView.annotations)
// HERE'S WHERE I SHOW THE ANNOTATIONS
uiView.showAnnotations(self.locations, animated: true)
}
static func dismantleUIView(_ uiView: MKMapView, coordinator: ()) {
uiView.removeAnnotations(uiView.annotations)
}
// MARK: Coordinator Class
func makeCoordinator() -> Coordinator {
return Coordinator(map: self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var map: Map
init(map: Map) {
self.map = map
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// Don't treat user as an annotation
if annotation is MKUserLocation {
return nil
}
// Check for reusable annotations
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: Constants.annotationReusedId)
// If none found, create a new one
if annotationView == nil {
annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: Constants.annotationReusedId)
annotationView!.canShowCallout = true
annotationView!.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
} else {
// Carry on with reusable annotation
annotationView!.annotation = annotation
}
return annotationView
}
}
}
Been searching for an answer for a while now and found nothing that worked well. I imagine there's a way to get visible map rect and then condition that in Map struct, but don't know how to do that. Thanks for reading this far!
Your delegate can implement mapView(_:regionDidChangeAnimated:) to be notified when the user finishes a gesture that changes the map's visible region. It can implement mapViewDidChangeVisibleRegion(_:) to be notified while the gesture is happening.
You can get the map's visible region by asking it for its region property. Regarding zoom levels, the region documentation says this:
The region encompasses both the latitude and longitude point on which the map is centered and the span of coordinates to display. The span values provide an implicit zoom value for the map. The larger the displayed area, the lower the amount of zoom. Similarly, the smaller the displayed area, the greater the amount of zoom.
Your updateUIView method recalculates the locations array every time SwiftUI calls it (because locations is a computed property). You should check how often SwiftUI is calling updateUIView and decide whether you need to cache the locations array.
If you want to efficiently find the locations in the visible region, try storing the locations in a quadtree.
Finally figured that out...
The Coordinator class can implement mapView(_:regionDidChangeAnimated:) (as #rob mayoff said) that gets called after the user finishes a gesture that changes the map's visible region. When that happens, annotations on the map and their array are updated. Looks something like this...
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if mapView.region.span.latitudeDelta < <Double that represents zoom> && mapView.region.span.longitudeDelta < <Double that represents zoom> {
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotations(map.getLocations(center: mapView.region.center))
}
}
... phrases (doubles missing from the if statement) in < > are to be replaced with your own code (the greater the double, the smaller zoom is needed to view the annotations). The array of annotations is updated by a function defined in Map struct and looks like this...
func getLocations(center: CLLocationCoordinate2D) -> [MKPointAnnotation] {
var annotations = [MKPointAnnotation]()
let annotationSpanIndex: Double = model.latlongDelta * 10 * 0.035
// Loop through all places
for place in data.dataList {
// If the place does have lat and long, create an annotation
if let lat = place.latitude, let long = place.longitude {
// Create annotations only for places within a certain region
if Double(lat)! >= center.latitude - annotationSpanIndex && Double(lat)! <= center.latitude + annotationSpanIndex && Double(long)! >= center.longitude - annotationSpanIndex && Double(long)! <= center.longitude + annotationSpanIndex {
// Create an annotation
let a = MKPointAnnotation()
a.coordinate = CLLocationCoordinate2D(latitude: Double(lat)!, longitude: Double(long)!)
a.title = place.adresa ?? ""
annotations.append(a)
}
}
}
return annotations
}
... where annotationSpanIndex determines in how big of a region around the center point will the annotations be shown (greater the index, bigger the region). This region should be ideally slightly larger than the zoom on which the annotations are shown.
I'm redoing the navigation of my app to be based on a custom UITabBarController. The tab bar opens the various ViewController. This is working fine, however Im now getting errors with the code in the ViewController that was previously working.
The new customTabBarController
import UIKit
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let feedController = feedVC() //Name of the view controller
let firstNavigationController = UINavigationController(rootViewController: feedController)
firstNavigationController.title = "Feed"
firstNavigationController.tabBarItem.image = UIImage(named: "feed_icon")
let clubController = moreVC() //Name of the view controller
let secondNavigationController = UINavigationController(rootViewController: clubController)
secondNavigationController.title = "Club"
secondNavigationController.tabBarItem.image = UIImage(named: "club_icon")
let recordController = moreVC() //Name of the view controller
let thirdNavigationController = UINavigationController(rootViewController: recordController)
thirdNavigationController.title = "Record"
thirdNavigationController.tabBarItem.image = UIImage(named: "record_icon")
let profileController = moreVC() //Name of the view controller
let fourthNavigationController = UINavigationController(rootViewController: profileController)
fourthNavigationController.title = "Profile"
fourthNavigationController.tabBarItem.image = UIImage(named: "profile_icon")
let moreController = moreVC() //Name of the view controller
let fifthNavigationController = UINavigationController(rootViewController: moreController)
fifthNavigationController.title = "More"
fifthNavigationController.tabBarItem.image = UIImage(named: "more_icon")
viewControllers = [firstNavigationController, secondNavigationController, thirdNavigationController, fourthNavigationController, fifthNavigationController]
tabBar.isTranslucent = false
// Color of menu bar set in AppDelegate.swift
}
}
feedVC
import UIKit
import Firebase
class feedVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
var activityArray = [Activity]()
var userdataArray = [Userdata]()
var cellSpacingHeight: CGFloat = 10 // Sets the spacing between the cells
override func viewDidLoad() {
super.viewDidLoad()
if Auth.auth().currentUser == nil {
let authVC = self.storyboard?.instantiateViewController(withIdentifier: "authVC") as? authVC
self.present(authVC!, animated: true, completion: nil)
}
tableView.delegate = self
tableView.dataSource = self
}
The error I get is:
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x102eb7b50) linked to the below code in the feedVC.
tableView.delegate = self
tableView.dataSource = self
The second error I get is related to recordVC
import UIKit
import MapKit
class recordVC: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var tileRenderer: MKTileOverlayRenderer!
// set initial location in Aspøya
let initialLocation = CLLocation(latitude: 63.011018, longitude: 7.914721)
// Set the zoom level of the location
let regionRadius: CLLocationDistance = 1000
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
regionRadius, regionRadius)
mapView.setRegion(coordinateRegion, animated: true)
}
func setupTileRenderer() {
// Fetching the map file from URL below. The {x}, {y}, and {z} are replaced at runtime by an individual tile’s coordinate. The z-coordinate, or zoom-level is specified by how much the user has zoomed in the map. The x and y are the index of the tile given the section of the Earth shown. A tile needs to be supplied for every x and y for each zoom level supported.
let template = "https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=norgeskart_bakgrunn&zoom={z}&x={x}&y={y}&format=image/png"
// Creates the overlay
let overlay = MKTileOverlay(urlTemplate: template)
// Indicates the tiles are opaque and replace the default map tiles
overlay.canReplaceMapContent = true
// Adds the overlay to the mapView
mapView.add(overlay, level: .aboveLabels)
// Creates a tile renderer which handles the drawing of the tiles.
tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay)
}
This give a similar error of: hread 1: EXC_BREAKPOINT (code=1, subcode=0x10561bb50) for this code line
// Adds the overlay to the mapView
mapView.add(overlay, level: .aboveLabels)
For the first one you have to use UITableViewDelegate and UITableViewDataSource like below :
class feedVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
....
}
For the second, You have to do UI operations on main thread.
DispatchQueue.main.async {
mapView.add(overlay, level: .aboveLabels)
}
currently my output is displaying selected values from pickerview inside textfields.
now my question is i want to access all these values on submit button and want to display in another view controller how to do this?. let me explain my scenario my first vc is set as collectionview from one of the collectionviewcell m redirecting to this page.
Note: i already know that how to pass data between two view controller. but its not working in my case.Please Help.
Code
#IBAction func StaffAtten_Action(_ sender: Any) {
// let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "StaffAttendence_SecondPage") as! StaffAttendence_SecondPage
//
// secondVC.a = active_text.text!
// secondVC.b = active_text.text!
// secondVC.c = active_text.text!
// secondVC.savedata.append(year.text!)
// secondVC.savedata.append(month.text!)
// secondVC.savedata.append(institute.text!)
}
}
The problem that you are having is that you are instantiating a brand new VC and passing data to it. The VC that is actually presented is not the one you created.
Since you have a segue connecting the two VCs, override prepare(for:sender:)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? StaffAttendence_SecondPage {
vc.a = ...
vc.b = ...
// pass the rest of the data here...
}
}
I have a storyboard with some text views on it. When I try to localize the storyboard (creating Storyboard.strings file), all of the UITextViews don't localize at all. UILabels are fine.
My config is as follow:
1 Base Storyboard file with 2 Storyboard.strings files
Storyboard Configuration
Storyboard.strings files are ok, its working with UILabels after all:
Storyboard.strings file
Have you found a solution for this problem?
I'm using Xcode 8.3, Swift 3, iOS 10.
You could just make a IBOutlet in Xcode and set the initial UITextView value in the didSet function.
E.g.
#IBOutlet var textView: UITextView {
didSet {
textView.text = NSLocalizedString("CUSTOM_LOCALISED_STRING", comment: "Comment.")
}
}
I ended up with this solution:
In a UIViewController I can access my UITextView-objects and force the localized text from Storyboard.strings to be used.
func fixTextViewStoryboardLocalization() {
guard storyboard != nil else {
return // No need to fix it if storyboard is not presented
}
let storyboardName = storyboard!.value(forKey: "name") as! String
for case let textView as UITextView in view.subviews {
if let ident = textView.restorationIdentifier {
textView.text = NSLocalizedString("\(ident).text", tableName: storyboardName, bundle: Bundle.main, value: "", comment: "")
}
}
}
Create a custom MyViewController (pick whatever name you want), call this function in viewDidLoad:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fixTextViewStoryboardLocalization()
}
}
Make sure the storyboard view controller is from class MyViewController:
MyViewController custom class in Storyboard
In order the code above to work, I need to set the Restoration ID for each UITextView in my Storyboard:
UITextView Restoration ID
Final step - localize the text view in Storyboard.strings:
/* Class = "UITextView"; text = "Base text"; ObjectID = "MyTextView-Restoration-ID"; */
"MyTextView-Restoration-ID.text" = "Localized text comes here";
This works for all my UITextViews in all my storyboards.
as we know, to implement PageTabBarController, we need to insert these code in AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
let viewControllers = [MatchDetailViewController(),ListPlayersViewController(),ChatViewController()]
window = UIWindow(frame: Device.bounds)
window!.rootViewController = MatchViewController(viewControllers: viewControllers, selectedIndex: 0)
window!.makeKeyAndVisible()
}
Now, i need to use PageTabBarController when i want to open detail for my match data. My question is, how to implement it without insert those code in AppDelegate.swift because it will open my MatchViewController (extend from PageTabBarController) for the first app launch.
I have tried this code, but it will cause Crash, and it pointed to my AppDelegate.swift
class MatchViewController: PageTabBarController {
var window: UIWindow?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
open override func prepare() {
super.prepare()
let viewControllers = [MatchDetailViewController(),ListPlayersViewController(),ChatViewController()]
//1st try: Crash
window = UIWindow(frame: Device.bounds)
window!.rootViewController = MatchViewController(viewControllers: viewControllers, selectedIndex: 0)
window!.makeKeyAndVisible()
//2nd try: error
self.rootViewController = MatchViewController(viewControllers: viewControllers, selectedIndex: 0)
//3rd try: crash
self.viewControllers = viewControllers
delegate = self
preparePageTabBar()
}
fileprivate func preparePageTabBar() {
pageTabBar.lineColor = Color.blue.base
pageTabBar.dividerColor = Color.blueGrey.lighten5
pageTabBarAlignment = PageTabBarAlignment.top
pageTabBar.lineAlignment = TabBarLineAlignment.bottom
}
}
extension MatchViewController: PageTabBarControllerDelegate {
func pageTabBarController(_ pageTabBarController: PageTabBarController, didTransitionTo viewController: UIViewController) {
}
}
Linked GitHub Question
Hi, yes there is a way. The PageTabBarController is inherited from aUIViewController`, which allows you to add it as a child of any other UIViewController. That said, you just gave me a great idea. I am going to make a new UIViewController that allows you to add as many child UIViewControllers, which will make this super easy to do. I will make this as a Feature Request.
Until the update, please use the suggested method of adding it as a child UIViewController. Are you familiar with how to do that?
First create AppToolbarController (subclass of ToolbarController) or you can use the one in the Material library demo.
And then from your view controller, you can use:
DispatchQueue.main.async {
let tabbarViewController = AppPageTabbarController(viewControllers: [vc1,vc2,vc3], selectedIndex: 0)
self.present(AppToolbarController(rootViewontroller: tabbarViewController))
}