passing instance of different sub classes onto another ViewController in Swift 3 - swift3

I am trying to pass the instance of a created user onto to another view controller. The problem is the user that is passed over depends on type of user is selected. What I mean is I have created many sub-class of the main class User. So for example: Guest, VIP, Child etc and so when an instance is created on first page it is the sub class for that user that is created. I cannot get my head around how I pass the user to the second view controller without the second view controller user property being set to Type User. The problem is type User does not have all properties that sub classes have so in second page I am unable to access all properties that a sub class might have. This is example how I currently have it set:
First screen / view controller
var entrantData: People?
var entrantSelected: EntrantType = .none
#IBAction func generatePass(_ sender: UIButton) {
do{
switch entrantSelected {
case .freechildguest: entrantData = try Child(dateOfBirth: "\(dobTextField.text!)")
case .classicguest: entrantData = ClassicGuest()
case .maintenance: entrantData = try Maintenance(NameAddress(firstName: firstNameTextField.text, lastName: lastNameTextField.text, streetAddress: streetAddressTextField.text, city: cityTextField.text, state: stateTextField.text, zipCode: zipCodeTextField.text, entrantType: .maintenance))
default: break
}
} catch let error {
showAlert(title: "Error", message: "\(error)")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destViewController = segue.destination as? TicketViewController {
destViewController.dataFromForm = entrantData
}
}
Second screen / view controller
class TicketViewController: UIViewController {
var dataFromForm: People?
So in second View controller I now have the user data in dataFromForm but I want it to be in its sub class so I can get its properties that are only divided in its sub class. E.g. maintenance user has firstname property that child and classicGuest do not.
I suppose I could create all properties that sub class use in the User super class but I feel that defeats the point creating sub classes that have their own unique properties.

One way to solve this issue is to keep the property of type People on the TicketViewController and when trying to access a property that only a certain subclass has, try conditional down casting the People instance to the appropriate subclasses.
Here's a basic example of how you can achieve this:
if let vip = dataFromForm as? VIP {
//access VIP properties
} else if let guest = dataFromForm as? Guest {
//access guest properties
}

Another thing you can do is down cast to the protocol itself. This may make it easier in certain situations when there are multiple subclasses conforming to the same protocol.
if let entrant = entrantData as? Nameable {
fullNameLabel.text = entrant.fullName
}
So the above code would not be executed on child or classicGuest since they do not conform to the Nameable protocol.

Related

NavigationSplitView doesn't work using enum with associated type

I have the following model in place for navigating to various pages:
enum Destination: Hashable {
case today
case activity
case settings(path: SettingsPath? = nil)
case project(project: ProjectItem? = nil)
enum SettingsPath: Hashable {
case cycles
case donations
}
}
In an ObservableObject, I'm using
#Published var sidebarDestination: Destination? = .today
And then in various NavigationLink buttons, I'm using the following initializer -
NavigationLink(value: NavigationModel.Destination.activity...
In the detail section of my NavigationSplitView I'm using a switch like so
detail: {
if let destination = navigationModel.sidebarDestination {
switch destination {
case .today:
TodayView()
case .project(let project):
// FIXME: Why is the detail view not updating
if let selectedProject = project {
IOSProjectDetailView(project: selectedProject)
} else {
EmptyView()
}
...
I've noticed that the pages with an enum case with an associated value are not updating correctly - the title on the page will update, but none of the other content. The pages with an enum case without an associated type seem to work just fine.
All of this is using an iPad - the larger screen sizes
I have tried with iOS 16.0.1 as well as iOS 16.1 and it is not working there either.
I would need to see your code in greater detail.
The 'selected' variable apparently is #Published var sidebarDestination: Destination? = .today.
That needs to be specified as such in the List definition (e.g. List(selection: $navigationModel.sidebarDestination) ). It is also very important that the types match = they appear to here.

Swift 3 pass button/gesture recognizer action to other function using selector

I am having a problem with Swift 3 action selectors - or, I can say, with gesture recognizers.
Suppose I am in a table view where I am having dynamic data through a table cell. I have one button or gesture recogniser in the table view and I want to send data from that button or gesture to a different function.
Please try the below function, Hope it will work for you!!
func whereYouWant(){
let button = UIButton()
button.accessibilityElements = [data1,data2]//Here you can pass any thing in this array.
button.addTarget(self, action: #selector(testFunction), for: .touchUpInside)
}
//Here you can call this function through selector....
func testFunction(myButton:UIButton){
let firstData = myButton.accessibilityElements?[0] as? //Here you can do type cast According to you
let secondData = myButton.accessibilityElements?[1] as? //Here you can do type Cast According to you.
}

Creating Hashtags in swift

I am new to swift. I am trying to create a view where you can create hashtags. There is a UITextfield in which you type the word to be converted. On press of 'enter' or '#', it should automatically convert to hashtags and display in labels which are further stored in an array format.
I tried many tutorials but none of them seem to work.
UITextField's have a delegate that is pretty handy.
A really simple implementation would be to use the textFieldShouldReturn delegate method, you can use this to detect when the return button is pressed.
Tell your view controller that it is going to adopt the protocol like this:
class ViewController:UIViewController, UITextFieldDelegate {
...
Then tell your textfield where to delegate it's events, if you are making the textfield inside the view controller then use self to reference the view controller like this:
let textField = UITextField()
textField.delegate = self // IMPORTANT
self.view.addSubView(textField)
Then inside your view controller implement the textFieldShouldReturn method like so:
class ViewController:UIViewController, UITextFieldDelegate {
...
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let text = textField.text {
let hashtag = "#\(text)"
print("New hashtag is \(hashtag)")
}
return true // allows the default behaviour of return button, you can return false if you want the keyboard to remain on screen.
}
}
This solution does not account for the user entering more than one hashtag, nor does it remove white space etc.. you may need to think about formatting/validating this string before it is useable.
You may also want to consider looking at other existing questions that cover things like splitting strings and creating arrays.

Mapbox Map for first time users with no connectivity

I need help with displaying a default mapView to first-time users with no network connectivity. Then as soon as the network is available, load the mapView using the styleURL.
The latter part is taken care of, however, I'm still struggling with displaying the mapView for the first-time users using the local styles I downloaded from Mapbox:
let localURL = Bundle.main.url(forResource: "style", withExtension: "json")
if Reachability.isConnectedToNetwork() == true {
mapView = MGLMapView(frame: view.bounds, styleURL: styleURL)
...
} else {
mapView = MGLMapView(frame: view.bounds, styleURL: localURL)
...
}
Am I missing something?

Posting with UIActivityViewController, but avoiding cropping?

Say you construct an image that is fullscreen on different devices. You then use UIActivityViewController to post to - for example - Instagram in the normal way.
The user clicks your share button, it brings up the usual iOS-sharing-thingy,
and you can post to Instagram (assuming the user's an Instagram user of course). No worries.
But typically the image is cropped on Instagram - you lose a little of the top and bottom.
Is there actually any solution for this?
Note that indeed - say you open the normal Photos app on the iPhone, and "share" and post on Instagram ... you lose a little of the top and bottom!
When the user does click the Instagram icon on this ...
in fact is there a way for me then to go back, be aware of the user's choice, and make the image the appropriate size?
Is there perhaps a way to pass a selection of images (various sizes) to the UIActivityViewController?
What's the deal on this, it seems like a basic failing?
Note - I'm fully aware that BEFORE going to the iOS-share-thingy, I could ask the user myself "What size image would you like me to make?"
Note - I'm aware that it's in some cases possible to post "directly" to say Instagram inside the app, without using Apple's share system; that's lame though.
To save anyone typing, here's some clean code to bring up the iOS-share system...
#IBAction func userClickedOurShareButton()
{
let s:[AnyObject] = [buildImage()]
let ac = CleanerActivity(activityItems:s, applicationActivities:nil)
ac.popoverPresentationController?.sourceView = view
// needed so that iPads won't crash. sarcasm: thanks Apple
ac.excludedActivityTypes = [UIActivityType.assignToContact,
UIActivityType.saveToCameraRoll,
UIActivityType.addToReadingList,
UIActivityType.copyToPasteboard ]
// consider UIActivityTypeMessage also
if #available(iOS 9.0, *) {
ac.excludedActivityTypes?.append(UIActivityType.openInIBooks)
} else {
// Fallback on earlier versions
}
self.present(ac, animated:false, completion:nil)
}
class CleanerActivity: UIActivityViewController {
func _shouldExcludeActivityType(_ activity: UIActivity) -> Bool {
let activityTypesToExclude = [
"com.apple.reminders.RemindersEditorExtension",
"com.apple.mobilenotes.SharingExtension",
"com.google.Drive.ShareExtension",
"com.apple.mobileslideshow.StreamShareService"
]
if let actType = activity.activityType {
if activityTypesToExclude.contains(actType.rawValue) {
return true
}
else if super.excludedActivityTypes != nil {
return super.excludedActivityTypes!.contains(actType)
}
}
return false
}
Disclaimer: this solution involves hard-coding Instagram's extension identifier into your app, which may or may not make it through app review, and may break in the future. Try at your own risk!
Apple provides a mechanism for this called UIActivityItemProvider. Instead of passing an image to your UIActivityViewController, you can pass subclass of UIActivityItemProvider that overrides itemForActivityType to return an appropriate image based on the activity type chosen by the user.
Apple provides constants for many common activity types, but Instagram isn't yet included. You can identify Instagram by checking if the activity type's raw value is com.burbn.instagram.shareextension. This would break if Instagram changed the ID of their extension.
Here's an UIActivityItemProvider that provides different images to Instagram:
class DynamicImageProvider: UIActivityItemProvider {
let instagramImage: UIImage
let defaultImage: UIImage
init(instagramImage: UIImage, defaultImage: UIImage) {
self.instagramImage = instagramImage
self.defaultImage = defaultImage
super.init(placeholderItem: defaultImage)
}
override func activityViewController(_ activityViewController: UIActivityViewController,
itemForActivityType activityType: UIActivityType) -> Any? {
if activityType.rawValue == "com.burbn.instagram.shareextension" {
return instagramImage
}
else {
return defaultImage
}
}
}
Then change the first two lines of your IBAction:
let imageProvider = DynamicImageProvider(instagramImage:buildInstagramImage(), defaultImage:buildImage())
let ac = CleanerActivity(activityItems:[imageProvider], applicationActivities:nil)