Well, SOAP is kinda old. But if for some reason you need to use it, here is how.

1. Create empty “Single View” project, you can call it “Swift-SOAP-with-Alamofire”
2. Create file named “Podfile” inside, your directory should look like this:
Zrzut ekranu 2016-08-04 o 12.31.15
3. Open “Podfile” and add following content:

use_frameworks!

target 'Swift-SOAP-with-Alamofire' do
pod 'Alamofire'
pod 'SWXMLHash'
pod 'AEXML'
pod 'StringExtensionHTML'
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['CONFIGURATION_BUILD_DIR'] = '$PODS_CONFIGURATION_BUILD_DIR'
    end
  end
end

4. From terminal execute “pod install” (if you don’t have or don’t know what this command does, feel free to google)

5. After command is done, you should see new “Swift-SOAP-with-Alamofire.xcworkspace” file, close your project and open this one instead.

6. At top of your ViewController.swift file add:

import Alamofire
import SWXMLHash
import StringExtensionHTML
import AEXML

7. Below, add new structure for your country:

struct Country {
    var name:String = ""
}

Your ads will be inserted here by

Easy Plugin for AdSense.

Please go to the plugin admin page to
Paste your ad code OR
Suppress this ad slot.

8. Inside ViewController create new function:

func getCountries(completion: (result: [Country]) -> Void) -> Void {
        
        var result = [Country]()
        let soapRequest = AEXMLDocument()
        let envelopeAttributes = ["xmlns:SOAP-ENV" : "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:ns1" : "http://www.webserviceX.NET"]
        let envelope = soapRequest.addChild(name: "SOAP-ENV:Envelope", attributes: envelopeAttributes)
        let body = envelope.addChild(name: "SOAP-ENV:Body")
        body.addChild(name: "ns1:GetCountries")
        
        let soapLenth = String(soapRequest.xmlString.characters.count)
        let theURL = NSURL(string: "http://www.webservicex.net/country.asmx")
        
        let mutableR = NSMutableURLRequest(URL: theURL!)
        mutableR.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
        mutableR.addValue("text/html; charset=utf-8", forHTTPHeaderField: "Content-Type")
        mutableR.addValue(soapLenth, forHTTPHeaderField: "Content-Length")
        mutableR.HTTPMethod = "POST"
        mutableR.HTTPBody = soapRequest.xmlString.dataUsingEncoding(NSUTF8StringEncoding)
        
        Alamofire.request(mutableR)
            .responseString { response in
                if let xmlString = response.result.value {
                    let xml = SWXMLHash.parse(xmlString)
                    let body =  xml["soap:Envelope"]["soap:Body"]
                    if let countriesElement = body["GetCountriesResponse"]["GetCountriesResult"].element {
                        let getCountriesResult = countriesElement.text!
                        let xmlInner = SWXMLHash.parse(getCountriesResult.stringByDecodingHTMLEntities)
                        for element in xmlInner["NewDataSet"]["Table"].all {
                            if let nameElement = element["Name"].element {
                                var countryStruct = Country()
                                countryStruct.name = nameElement.text!
                                result.append(countryStruct)
                            }
                        }
                    }
                    completion(result: result)
                }else{
                    print("error fetching XML")
                }
        }
    }

Where actual magic happens.

9. Open your Info.plist as “Code view” and add this to allow loading from HTTP (unless your server has SSL working):

<key>NSAppTransportSecurity</key>

<dict>

<key>NSAllowsArbitraryLoads</key>

<true/>

</dict>

10. Just call:

        self.getCountries { (result) in
            print(result)
        }

and your result will be populated.

You can checkout example project from github at:
https://github.com/blastar/Swift-SOAP-with-Alamofire

Keep in mind that my code is only a way to use, for real apps, you should for example use guard instead of multiple “if let”.

When you dive into .map and .flatMap ocean, you may be confused what is the difference. Consider such example:

let arr = [1,2,3,4,5,6]
print(arr.map{ return String($0 * 2)+"x" })
print(arr.flatMap{ return String($0 * 2)+"x" })

Both print’s, will give you same result:

["2x", "4x", "6x", "8x", "10x", "12x"]

So why bother? The main details between them, is that flatMap will skip nil values and unwraps them, so the example:

let mapArr = arr.map{ (string:Int) -> String? in
    if string < 2 {
        return nil
    }
    return String(string * 2)+"x"
}
print(mapArr)
let mapArr2 = arr.flatMap{ (string:Int) -> String? in
    if string < 2 {
        return nil
    }
    return String(string * 2)+"x"
}
print(mapArr2)

will give following results:

[nil, Optional("4x"), Optional("6x"), Optional("8x"), Optional("10x"), Optional("12x")]
["4x", "6x", "8x", "10x", "12x"]

As you can see now .map output are all optionals with nil as first value is skipped. In .flatMap each value is unwrapped to String and there is no nil values at all.

Testing is good practice, but sometimes you may get into trap. Opposite to Objective-C, in Swift 2.x you don’t need to add each file to your test target, even more – you should not do this at all!

Using Swift 1.x developers had to make every testable class and methods public, which was very annoying. Now, all you have to do, is use:

@testable import ProjectName

in each of your test files (this is required for both unit testing and UI testing). But there is one catch – what if your project name contains spaces like “My First Swift Project”? Fortunately there is simple solution, in most cases just replace space with underscore, but if you want to be sure, go to your app’s target, then “Build settings”, and search for “Module name” – this is string you should use in import.

Swift unit/UI testing

Sooner or later you will face with “Invalid predicate: nil RHS”. What does it mean? Fortunately solution is very simple, somewhere in your code, you are using NSPredicate with “CONTAINS” while your search text is nil. All you have to do is make sure to check and validate text entered by user.

The common mistake is using more then one “@” placeholders, while having only one parameter.

I pretty sure all of you know how to do it in Objective C, but in Swift 2.x this is even simpler.

First you need to subscribe to  keyboardWillShow and keyboardWillHide events, which is achieved a bit different then in ObjC:

NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyViewController.keyboardWillShow), name:UIKeyboardWillShowNotification, object: nil);

NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyViewController.keyboardWillHide(_:)), name:UIKeyboardWillHideNotification, object: nil);

Since we are using Swift 2.2 here, note different #selector. Where “MyViewController” is you view controller class name. Next and last step is to add functions for handling screen movement:

func keyboardWillShow(notification: NSNotification) {
        let keyboardHeight = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey]?.CGRectValue.height
        UIView.animateWithDuration(0.1, animations: { () -> Void in
            self.view.window?.frame.origin.y = -1 * keyboardHeight!
            self.view.layoutIfNeeded()
        })
    }
func keyboardWillHide(notification: NSNotification) {
        UIView.animateWithDuration(0.1, animations: { () -> Void in
            self.view.window?.frame.origin.y = 0
            self.view.layoutIfNeeded()
        })
    }

This will work without adding any scroll views, just out of the box.

Yes, finally new major version is released. Mostly one thing changed – CoreData is now used and storage with iCloud synchronization support. Now all your devices will be nicely synchronized. Currently branch 1.3.x will be freezed and tvOS version will be developed.

Sooner or later you will face with changing back button title in navigation bar. This may seem simple. But after few hours trying to change this damn text, you will change your mind. I have no idea why this is designed this way, but answer for question “How can I change back button text in current view controller?” is – you cannot. So how to do it without overriding whole navigation bar? All you have to do is change title of backBarButtonItem in PREVIOUS view controller. But there is another catch – you can’t just do :

self.navigationItem.backBarButtonItem.title = @"" 

becouse this won’t work. Instead of this, you need to:

UIBarButtonItem* backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.backBarButtonItem = backBarButtonItem;

And keep in mind – this has to be done in PREVIOUS controller, not the one you need to change your back button text. The alternative is to give up on native back button and create your own with:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@"My back button" style:UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.hidesBackButton = YES;
self.navigationItem.leftBarButtonItem = backButton;

This can seem trivial, but I saw lot of people complaining about not working tint color when using UIImage. Here is the proper way:

UIImageView *imageView = [[UIImageView alloc] init];
UIImage *image = [UIImage imageNamed:@"my_image"];
imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[imageView setTintColor:[UIColor redColor]];

The point is – “imageWithRenderingMode” method just returns new image with rendering mode set, it does not changes anything in image object itself.

Sometimes, especially if you are using custom plugins to import stock data, you may find products with some stock value (greater then 0) but with stock flag set to “out of stock”. How to find them? Using WP_Query of course:

Notice ‘post_type’ => ‘product_variation’ – if you are not using variations, just change it to ‘product’.

This can be really frustrating, you wanted your woocommerce shop running multilingual, but from cart page “Checkout” button leads to default language version? Solution is very simple, but not elegant – so far did not found another solution. First step is to create all woocommerce pages in every language you need, this is very simple but can take some time. Then, in your child theme (remember not to make any modifications to theme you are using!), in functions.php add such code:

All those functions are using same pll_get_post from Polylang plugin, to translate your default page ID you set in Woocommerce/Settings/Checkout/Checkout Pages to their translated version. Hope you will find it useful.