How to send MSMessage in Messages Extension? - ios

I want to implement an imessage app, however being new to the messages framework and iMessage apps being such a new thing there aren't many resources. So I am following the WWDC video and using Apples providing sample app for a guide.
I have three views, the MessageViewController which handles pretty much all the functionality and then a CreateViewController and a DetailsViewController.
I am simply trying to create an MSMessage from the CreateViewController and display in the DetailsViewController.. then add to the data.
However I get a crash when trying to create the data.
#IBAction func createAction(_ sender: AnyObject) {
//present full screen for create list
self.delegate?.createViewControllerDidSelectAdd(self as! CreateViewControllerDelegate)
}
The data type I am trying to pass is the dictionary from a struct:
struct data {
var title: String!
var date: Date!
var dictionary = ["title" : String(), "Array1" : [String](), "Array2" : [String]() ] as [String : Any]
}
So here's how things are set up;
MessagesViewController
class MessagesViewController: MSMessagesAppViewController, {
// MARK: Responsible for create list button
func composeMessage(for data: dataItem) {
let messageCaption = NSLocalizedString("Let's make", comment: "")
let dictionary = data.dictionary
func queryItems(dictionary: [String:String]) -> [URLQueryItem] {
return dictionary.map {
URLQueryItem(name: $0, value: $1)
}
}
var components = URLComponents()
components.queryItems = queryItems(dictionary: dictionary as! [String : String])
let layout = MSMessageTemplateLayout()
layout.image = UIImage(named: "messages-layout-1.png")!
layout.caption = messageCaption
let message = MSMessage()
message.url = components.url!
message.layout = layout
message.accessibilityLabel = messageCaption
guard let conversation = activeConversation else { fatalError("Expected Convo") }
conversation.insert(message) { error in
if let error = error {
print(error)
}
}
}
}
extension MessagesViewController: CreateViewControllerDelegate {
func createViewControllerDidSelectAdd(_ controller: CreateViewControllerDelegate) {
//CreatesNewDataItem
composeMessage(for: dataItem())
}
}
CreateViewController
/**
A delegate protocol for the `CreateViewController` class.
*/
protocol CreateViewControllerDelegate : class {
func createViewControllerDidSelectAdd(_ controller: CreateViewControllerDelegate)
}
class CreateViewController: UIViewController {
static let storyboardIdentifier = "CreateViewController"
weak var delegate: CreateViewControllerDelegate?
#IBAction func create(_ sender: AnyObject) {
//present full screen for create list
self.delegate?.createViewControllerDidSelectAdd(self as! CreateListViewControllerDelegate)
}
}
Would someone show where I am going wrong and how I can send a MSMessage? If I am able to send the message I should then be able to receive and resend.

One issue I see, without being able to debug this myself:
you are setting your components.queryItems to your dictionary var cast as [String:String], but the dictionary returned from data.dictionary is not a [String:String], but a [String:Any]
In particular, dictionary["Array1"] is an array of Strings, not a single string. Same for dictionary["Array2"]. URLQueryItem expects to be given two strings in its init(), but you're trying to put a string and an array of strings in (though I'm not sure that you're actually getting to that line in your queryItems(dictionary:) method.
Of course, your dataItem.dictionary is returning a dictionary with 4 empty values. I'm not sure that's what you want.

Related

How to re-initialize a lazy property from another view?

I have data that I want to read from disk into memory that takes a nontrivial amount of time.
I want to be able to do two things:
I don't the data to be read every time the view loads.
I want to be able to invoke it from another view.
lazy var data: [String: String] = {
guard let data = readFromDisk() else { return [:] }
return processData(data: data)
}()
Above code gets initialized only once when the view loads for the first time, which is perfect for eliminating unnecessary computation. The problem is I also want to be able to trigger it from another view when needed.
I tried to trigger re-initialization:
func getData() {
guard let data = readFromDisk() else { return [:] }
data = processData(data: data)
}
and invoke it from another view:
let vc = ViewController()
vc.getData()
but, doesn't work.
I tried to see if I could use static since it's also lazy, but I get an error saying:
Instance member cannot be used on type 'ViewController'
Finally, I tried creating a separate class:
class DataImporter {
var data: [String: String] {
guard let data = readFromDisk() else { return [:] }
return processData(data: data)
}
func readFromDisk() -> [String: String] {}
func processData(data: [String: String]) -> [String: String] {}
}
and have the lazy property in ViewController:
lazy var importer = DataImporter()
thinking that instantiating a class achieves the dual effect of taking advantage of a lazy property and invoking it when needed:
let vc = ViewController()
vc.importer = DataImporter()
This instantiates the class about a hundred times for some reason which is not ideal.
I would suggest creating a function that loads the data into data and then whenever you need to reload data, simply reassign it.
class DataStore {
lazy var data: [String: String] = loadData()
func readFromDisk() -> Data? {...}
func processData(data: Data) -> [String:String] { ... }
func loadData() -> [String:String] {
guard let data = readFromDisk() else { return [:] }
return processData(data: data)
}
}
let store = DataStore()
let data = store.data // only loaded here
store.data = store.loadData() // reloads the data
If you don't want the loadData function to be exposed, you can also create a separate reloadData function.
class DataStore {
...
func reloadData() {
data = loadData()
}
}
and then instead of doing store.data = store.loadData(), simply call store.reloadData()

Passing textfield text value Swift 4

i have three viewcontrollers in signing up for users,
and i want to pass my textfield values from button to another viewcontroller's button for there to finish the registration. i tried everything that i know but no luck
here is my button from firstViewController
#IBAction func nextButtonTapped(_ sender: Any) {
let parameters:Parameters=[
"name":nameTextfield.text!,
"phone":phoneTextfield.text!
]
}
and here is thirdViewController's button action
#IBAction func registerButtonTapped(_ sender: Any) {
//Sending http post request
Alamofire.request(URL_USER_REGISTER, method: .post, parameters: parameters).responseJSON {
response in
//printing response
print(response)
//getting the json value from the server
if let result = response.result.value {
//converting it as NSDictionary
let jsonData = result as! NSDictionary
//displaying the message in label
//self.labelMessage.text = jsonData.value(forKey: "message") as! String?
}
}
Try these steps
1.Create a model with static variables
import Foundation
class Model
{
static var textA:String?
static var textB:String?
static var textC:String?
}
2. assign the value to the model when your click the button
Model.textA = textField.text!
3. Access the value from third ViewController
let parameters:Parameters=[
"name":Model.textA!,
"phone":Model.textA!
]

How to send multiple variables through segue

How can I send multiple variables through a segue in Swift? The QBBust gets sent over fine and prints on the view controller, but the QBName doesn't get sent over for some reason. Can anyone spot why?
if let send = sender as? Double{
destination.QBBust = send
}
if let sent = sender as? String{
destination.QBName = sent
}
}
}
private var _QBName:String!
var QBName: String{
get{
return _QBName
} set {
_QBName = newValue
}
}
private var _QBBust:Double!
var QBBust: Double {
get {
return _QBBust
} set{
_QBBust = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
let bust = String(Int(_QBBust))
QBBustLabel.text = "\(bust)%"
QBNameLabel.text = _QBName
}
This next part is in the button function that triggers the segue
performSegue(withIdentifier: "QBResultVC", sender: QBBust)
performSegue(withIdentifier: "QBResultVC", sender: QBName)
As in Tiago's answer, you can create a new struct or class which has QBName and QBBust properties. In addition, you can also use tuple in Swift.
This is an example:
in Destination ViewController
declare var QBInfo:(name: String, bust: Double)?
and in the button function that triggers the segue
let QBInfo = (name: QBName, bust: QBBust)
performSegue(withIdentifier: "QBResultVC", sender: QBBust)
then in prepareForSegue:sender:method
destination.QBInfo = QBInfo
This question is duplicate, but you can create a Struct or new Class, and storage your data how properties and send the 'transport' object in segue.
For detail, look this answser:
Swift sending Multiple Objects to View Controller

Write Generic Swift Method to load data from property list

I have a method that loads an array of dictionaries from a propertylist. Then I change those arrays of dictionaries to array of a defined custom type;
I want to write that method in generic form so I call that method with the type I expect, then the method loads it and returns an array of my custom type rather than dictionaries
func loadPropertyList(fileName: String) -> [[String:AnyObject]]?
{
if let path = NSBundle.mainBundle().pathForResource(fileName, ofType: "plist")
{
if let plistXML = NSFileManager.defaultManager().contentsAtPath(path)
{
do {
if let temp = try NSPropertyListSerialization.propertyListWithData(plistXML, options: .Immutable, format: nil) as? [[String:AnyObject]]
{
return temp
}
}catch{}
}
}
return nil
}
//
func loadList<T>(fileName: String) -> [T]?{//**Here the answer I am expecting**}
I am assuming your function to read from a Plist works and that you don't want to subclass NSObject.
Since Swift reflecting does not support setting values this is not possible without some implementation for each Type you want this to work for.
It can however be done in a pretty elegant way.
struct PlistUtils { // encapsulate everything
static func loadPropertyList(fileName: String) -> [[String:AnyObject]]? {
if let path = NSBundle.mainBundle().pathForResource(fileName, ofType: "plist") {
if let plistXML = NSFileManager.defaultManager().contentsAtPath(path) {
do {
if let temp = try NSPropertyListSerialization.propertyListWithData(plistXML, options: .Immutable, format: nil) as? [[String:AnyObject]] {
return temp
}
} catch {
return nil
}
}
}
return nil
}
}
This protocol will be used in a generic fashion to get the Type name and read the corresponding Plist.
protocol PListConstructible {
static func read() -> [Self]
}
This protocol will be used to implement Key Value setters.
protocol KeyValueSettable {
static func set(fromKeyValueStore values:[String:AnyObject]) -> Self
}
This is the combination of both to generate an array of objects. This does require that the Plist is named after the Type.
extension PListConstructible where Self : KeyValueSettable {
static func read() -> [Self] {
let name = String(reflecting: self)
var instances : [Self] = []
if let data = PlistUtils.loadPropertyList(name) {
for entry in data {
instances.append(Self.set(fromKeyValueStore: entry))
}
}
return instances
}
}
This is some Type.
struct Some : PListConstructible {
var alpha : Int = 0
var beta : String = ""
}
All you have to do is implement the Key Value setter and it will now be able to be read from a Plist.
extension Some : KeyValueSettable {
static func set(fromKeyValueStore values: [String : AnyObject]) -> Some {
var some = Some()
some.alpha = (values["alpha"] as? Int) ?? some.alpha
some.beta = (values["beta"] as? String) ?? some.beta
return some
}
}
This is how you use it.
Some.read()

Refreshing data in Watch App from App Group cached on iPhone

To pass data from my IOS App to my Watch app, I am caching the data into NSUserDefaults (in an App Group) in my iOS AppDelegate. (I call this method both in didFinishLaunching and willTerminate.)
I then pass this data to my Watch app via the App Group and all works well.
The problem is that when I change the data on my iPhone app I want to update the data on my Watch App as well, but it is not updated unless I uninstall and reinstall the Watch App.
I have tried my fetch from App Groups method in both awakeWithContext and willActivate but the data is still stale. How can I refresh the data on the Watch short of uninstalling/reinstalling?
*edit: In other words, the Apple Watch app apparently keeps running on the watch... whereas I would prefer that it was killed, so that when the user changes data on the iPhone app, then taps on the watch app it opens with the new data, instead of just awaking with the stale data.
class InterfaceController: WKInterfaceController {
var nameList: [String]?
var currentUserName: String?
var peopleDict: [String : [String : [String : String]]]?
#IBOutlet weak var personTable: WKInterfaceTable!
private func loadTableData() {
if let list = nameList {
personTable.setNumberOfRows(list.count, withRowType: "PersonTableRowController")
for (index, personName) in enumerate(list) {
if let row = personTable.rowControllerAtIndex(index) as? PersonTableRowController {
row.personLabel.setText(personName)
} else { println("Could not cast as PersonTableRowController") }
}
}
}
func fetchFromAppGroup() {
let defaults = NSUserDefaults(suiteName: "group.com.myndarc.thredz2")
defaults?.synchronize()
if let fetchedPeopleDict: AnyObject = defaults?.valueForKey("peopleDict") {
//save peopleDict locally
peopleDict = fetchedPeopleDict as? [String : [String : [String : String]]]
if let dict = fetchedPeopleDict as? NSDictionary {
nameList = dict.allKeys as? [String]
}
}
if let user: AnyObject = defaults?.valueForKey("currentUser") {
currentUserName = user as? String
}
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
override func willActivate() {
super.willActivate()
fetchFromAppGroup()
loadTableData()
}

Resources